Compare commits

...

27 Commits

Author SHA1 Message Date
mid
9160c8ce35 Forgot to add this 2026-01-18 13:42:50 +02:00
mid
2175d32da4 Many bug fixes 2026-01-18 13:42:43 +02:00
Mid
c44e58c4ac State machine bug fixed 2026-01-14 11:37:59 +02:00
Mid
63d641cc82 Texture API improvements
1. Selective mipmapping
2. Manual mipmap generation (actually produced better results at least
for alpha-tested foliage)
2026-01-14 11:27:03 +02:00
Mid
a571d2d999 Move to runtime generated SDF fonts
GlyphCache is introduced as a layer that handles the bin packing with a
shelf heuristic. SDF rendering is handled in the fixed-function pipeline
using a simple alpha test. The shader version also applies a little
smoothing.

k3Batch had to be updated to add a minimum alpha parameter.
2025-12-01 23:58:55 +02:00
Mid
8207743308 Assign textures in depth-only pass (fixed state desync bug) 2025-12-01 23:55:17 +02:00
Mid
b532ccd68f Begin transition to dynamic glyph cache 2025-11-24 11:30:14 +02:00
Mid
6cbd201b63 Huge load
1. k3Update added, which must be called per frame.
2. Added GPU timers for profiling.
3. Added ARB_direct_state_access support because Mesa is being a bitch
again.
4. Cached uniform locations in an open-addressing hash table.
Unfortunately, I'm pretty sure there was no performance increase, at
least on my development machine, but it shouldn't hurt anywhere else.
2025-10-12 20:46:56 +03:00
Mid
2ebab9358d Shadowmap size limit 2025-10-12 20:43:17 +03:00
Mid
69f355eae4 Up max index count to 4 gigs 2025-09-30 11:03:49 +03:00
Mid
676127c41f Correct drawing order 2025-09-19 23:11:19 +03:00
Mid
9ae4abbb56 memcpy -> memmove 2025-09-19 23:11:12 +03:00
Mid
0461199e64 Prefer size property over struct field 2025-09-19 23:11:03 +03:00
Mid
05d0c75937 Linear layout should acknowledge width property 2025-09-19 23:10:44 +03:00
Mid
1ee7d6c26e Fix font boundary 2025-09-19 23:10:25 +03:00
Mid
9bc549546f styling inheritance bugfix 2025-09-13 12:01:25 +03:00
Mid
2cb80f8abe GL bugfixes 2025-09-13 12:01:11 +03:00
Mid
6c13a83271 16-byte alignment 2025-09-13 12:00:44 +03:00
Mid
4afe4f3fbe Mipmapping & texture compression options 2025-09-13 12:00:29 +03:00
Mid
5e0966beb8 Text fixes 2025-09-13 11:59:12 +03:00
Mid
c3daf57f24 Adapt heightForWidth from Qt for text wrapping 2025-08-28 23:34:05 +03:00
Mid
bf60cac302 Menu mouse capturing 2025-08-28 19:14:02 +03:00
Mid
6d5ba6037c Add scrollbar and image objects, cropping, layout properties 2025-08-28 14:13:08 +03:00
Mid
295c882100 Animation trees 2025-08-10 16:09:13 +03:00
Mid
cc37e17046 immdraw.h 2025-07-27 16:18:23 +03:00
Mid
c8e6c320f1 Add layouts and decouple immediate drawings 2025-07-27 16:17:24 +03:00
Mid
3bb8a63ee1 Frustum culling 2025-07-27 16:16:45 +03:00
16 changed files with 2257 additions and 507 deletions

388
src/glyphcache/glca.h Normal file
View File

@@ -0,0 +1,388 @@
/*
* This is a mostly standalone file that implements an SDF glyph cache.
* It can support any graphics backend as long as it can modify and load subtextures.
*
* It is SDF-only since this way it is less critical to support different font sizes.
*
* Example initialization:
* FT_Library ftlib;
* FT_Init_FreeType(&ftlib);
*
* FT_Property_Set(ftlib, "sdf", "spread", &(int) {24});
*
* FT_Face face;
* assert(FT_New_Face(ftlib, "my font file.ttf", 0, &face) == 0);
* FT_Select_Charmap(face, FT_ENCODING_UNICODE);
* FT_Set_Pixel_Sizes(face, 64, 0);
*
* void *userdata = NULL;
*
* GlyphCache gc = {};
* glca_init(&gc, face, 2048, 2048, userdata, my_set_image, my_get_image, my_fill_custom_data);
*
* my_fill_custom_data can be used in case you need glyph metrics.
* If GLCA_CUSTOM_GLYPH_DATA is not defined, my_fill_custom_data is unused.
*
* Example usage:
* // Should be called regularly (recommended once per frame)
* glca_set_time(&gc, get_time_now_somehow());
* ...
* // When you need to render text:
* GlyphCacheGlyph *glyph = glca_request(&gc, 'Ь');
* if(!glyph) {
* // Skip
* }
*
* Safety padding is added (in height) to each new row. It is by default
* zero but it is a good idea to experiment with other values.
*
* WARNING: GlyphCache relies on a few heuristics that assume the following:
* 1. Glyphs are roughly equal in size (true for text)
* 2. The atlas is reasonably larger in area than a single glyph (at least ~100x)
* If either does not hold, GlyphCache will perform AWFULLY.
*/
#ifndef GLCA_H
#define GLCA_H
#include<freetype/freetype.h>
#include<unistd.h>
#include<assert.h>
#include<stdint.h>
#include<stddef.h>
#include<stdbool.h>
#ifndef GLCA_CUSTOM_GLYPH_DATA
typedef struct GlyphCacheGlyphData {} GlyphCacheGlyphData;
#endif
typedef struct GlyphCacheGlyph {
uint32_t codepoint;
uint16_t x;
uint16_t y;
uint16_t w;
uint16_t h;
GlyphCacheGlyphData data;
uint64_t last_use;
} GlyphCacheGlyph;
typedef struct GlyphCacheRow {
size_t y;
size_t width;
size_t height;
size_t entry_count;
uint32_t *entries;
} GlyphCacheRow;
struct GlyphCache;
typedef void(*GlyphCacheSetImage)(struct GlyphCache *gc, int x, int y, int w, int h, const void *buf);
typedef void(*GlyphCacheGetImage)(struct GlyphCache *gc, int x, int y, int w, int h, void *buf);
typedef void(*GlyphCacheFillCustomData)(struct GlyphCache *gc, FT_GlyphSlot, GlyphCacheGlyph*);
typedef struct GlyphCache {
FT_Face face;
uint64_t current_time;
int safety_padding;
size_t total_width;
size_t total_height;
size_t row_count;
GlyphCacheRow *rows;
size_t item_count;
size_t item_capacity;
GlyphCacheGlyph *items;
void *userdata;
GlyphCacheSetImage set_image;
GlyphCacheGetImage get_image;
GlyphCacheFillCustomData fill_custom_data;
} GlyphCache;
int glca_init(GlyphCache*, FT_Face, size_t w, size_t h, void *userdata, GlyphCacheSetImage, GlyphCacheGetImage, GlyphCacheFillCustomData);
GlyphCacheGlyph *glca_get_noupdate(GlyphCache *gc, uint32_t codepoint);
GlyphCacheGlyph *glca_get(GlyphCache *gc, uint32_t codepoint);
GlyphCacheGlyph *glca_request(GlyphCache *gc, uint32_t codepoint);
void glca_try_evict(GlyphCache *gc);
void glca_set_safety_padding(GlyphCache *gc, int safety_padding);
void glca_set_time(GlyphCache *gc, uint64_t time);
#endif
#ifdef GLCA_IMPLEMENTATION
static uint32_t glca_hash(uint32_t x) {
x = ((x >> 16) ^ x) * 0x45d9f3bu;
x = ((x >> 16) ^ x) * 0x45d9f3bu;
x = (x >> 16) ^ x;
return x;
}
int glca_init(GlyphCache *gc, FT_Face face, size_t total_width, size_t total_height, void *userdata, GlyphCacheSetImage set_image, GlyphCacheGetImage get_image, GlyphCacheFillCustomData fill_custom_data) {
memset(gc, 0, sizeof(*gc));
gc->face = face;
gc->total_width = total_width;
gc->total_height = total_height;
gc->userdata = userdata;
gc->set_image = set_image;
gc->get_image = get_image;
gc->fill_custom_data = fill_custom_data;
gc->item_capacity = 128;
gc->items = calloc(gc->item_capacity, sizeof(*gc->items));
return 0;
}
// Returns row number for new glyph. If == -1, impossible.
static int choose_row(GlyphCache *gc, size_t w, size_t h) {
size_t y = 0;
if(gc->row_count == 0) {
goto make_new;
}
bool found = false;
int bestRow = -1;
int bestRowHDiff = 10000000;
for(int row = 0; row < gc->row_count; row++) {
y += gc->rows[row].height;
if(gc->total_width - gc->rows[row].width < w) {
// No width remains in row.
continue;
}
if(gc->rows[row].height < h) {
// Row too short.
continue;
}
int diff = gc->rows[row].height - h;
if(bestRowHDiff > diff) {
bestRowHDiff = diff;
bestRow = row;
found = true;
}
}
if(found) {
return bestRow;
}
make_new:
if(y + h > gc->total_height) {
return -1;
}
h += gc->safety_padding;
if(y + h > gc->total_height) {
h = gc->total_height - y;
}
int row = gc->row_count;
gc->rows = realloc(gc->rows, sizeof(*gc->rows) * (++gc->row_count));
memset(&gc->rows[row], 0, sizeof(*gc->rows));
gc->rows[row].y = y;
gc->rows[row].height = h;
return row;
}
GlyphCacheGlyph *glca_get_noupdate(GlyphCache *gc, uint32_t codepoint) {
uint32_t i = glca_hash(codepoint);
for(size_t probe = 0; probe < 5; probe++) {
i &= gc->item_capacity - 1;
if(gc->items[i].codepoint == 0 && gc->items[i].w == 0) {
break;
}
if(gc->items[i].codepoint == codepoint) {
return &gc->items[i];
}
i++;
}
return NULL;
}
GlyphCacheGlyph *glca_get(GlyphCache *gc, uint32_t codepoint) {
GlyphCacheGlyph *glyph = glca_get_noupdate(gc, codepoint);
if(glyph) {
glyph->last_use = gc->current_time;
}
return glyph;
}
void glca_try_evict(GlyphCache *gc) {
uint64_t min_time = gc->current_time, max_time = 0;
for(size_t i = 0; i < gc->item_count; i++) {
if(gc->items[i].codepoint == 0) {
continue;
}
if(min_time > gc->items[i].last_use) {
min_time = gc->items[i].last_use;
}
if(max_time < gc->items[i].last_use) {
max_time = gc->items[i].last_use;
}
}
uint64_t threshold = max_time / 2 + min_time / 2;
for(size_t r = 0; r < gc->row_count; r++) {
GlyphCacheRow *gcr = &gc->rows[r];
size_t x_shift = 0;
for(size_t i = 0; i < gcr->entry_count;) {
GlyphCacheGlyph *glyph = glca_get_noupdate(gc, gcr->entries[i]);
if(x_shift) {
void *buf = malloc(glyph->w * glyph->h);
gc->get_image(gc, glyph->x, glyph->y, glyph->w, glyph->h, buf);
glyph->x -= x_shift;
gc->set_image(gc, glyph->x, glyph->y, glyph->w, glyph->h, buf);
free(buf);
}
if(glyph->last_use >= threshold) {
// Do not evict.
i++;
continue;
}
x_shift += glyph->w;
memmove(gcr->entries + i, gcr->entries + i + 1, sizeof(*gcr->entries) * (gcr->entry_count - i - 1));
gcr->width -= glyph->w;
gcr->entry_count--;
glyph->codepoint = 0;
}
}
}
static void glca_expand(GlyphCache *gc, size_t shift) {
size_t new_capacity = gc->item_capacity << shift;
GlyphCacheGlyph *newarray = calloc(new_capacity, sizeof(*newarray));
for(size_t idx = 0; idx < gc->item_capacity; idx++) {
if(gc->items[idx].codepoint == 0) {
continue;
}
uint32_t i = glca_hash(gc->items[idx].codepoint);
size_t probe;
for(probe = 0; probe < 5; probe++) {
i &= new_capacity - 1;
if(newarray[i].codepoint == 0) {
newarray[i] = gc->items[idx];
break;
}
i++;
}
if(probe == 5) {
goto failed_to_expand;
}
}
free(gc->items);
gc->item_capacity = new_capacity;
gc->items = newarray;
return;
failed_to_expand:
free(newarray);
glca_expand(gc, shift + 1);
}
GlyphCacheGlyph *glca_request(GlyphCache *gc, uint32_t codepoint) {
GlyphCacheGlyph *glyph = glca_get(gc, codepoint);
if(glyph) {
return glyph;
}
assert(FT_Load_Char(gc->face, codepoint, 0) == 0);
assert(FT_Render_Glyph(gc->face->glyph, FT_RENDER_MODE_NORMAL) == 0);
assert(FT_Render_Glyph(gc->face->glyph, FT_RENDER_MODE_SDF) == 0);
size_t w = gc->face->glyph->bitmap.width, h = gc->face->glyph->bitmap.rows;
int row = choose_row(gc, w, h);
if(row == -1) {
glca_try_evict(gc);
row = choose_row(gc, w, h);
if(row == -1) {
return NULL;
}
}
GlyphCacheRow *gcr = &gc->rows[row];
size_t x = gcr->width;
size_t y = gcr->y;
gcr->width += w;
gcr->entries = realloc(gcr->entries, sizeof(*gcr->entries) * (gcr->entry_count + 1));
gcr->entries[gcr->entry_count++] = codepoint;
glyph = NULL;
while(1) {
uint32_t i = glca_hash(codepoint);
for(size_t probe = 0; probe < 5; probe++) {
i &= gc->item_capacity - 1;
if(gc->items[i].codepoint == 0) {
glyph = &gc->items[i];
break;
}
i++;
}
if(glyph) {
break;
}
glca_expand(gc, 1);
}
*glyph = (GlyphCacheGlyph) {
.codepoint = codepoint,
.x = x,
.y = y,
.w = w,
.h = h,
};
#ifdef GLCA_CUSTOM_GLYPH_DATA
if(gc->fill_custom_data) {
gc->fill_custom_data(gc, gc->face->glyph, glyph);
}
#endif
uint8_t *buf = malloc(w * h);
for(int y = 0; y < h; y++) {
memcpy(buf + y * w, gc->face->glyph->bitmap.buffer + y * gc->face->glyph->bitmap.pitch, w);
}
gc->set_image(gc, x, y, w, h, buf);
free(buf);
return glca_get(gc, codepoint);
}
void glca_set_safety_padding(GlyphCache *gc, int safety_padding) {
gc->safety_padding = safety_padding;
}
void glca_set_time(GlyphCache *gc, uint64_t time) {
gc->current_time = time;
}
#endif

64
src/immdraw.h Normal file
View File

@@ -0,0 +1,64 @@
#pragma once
#include"k3batch.h"
#include"k3font.h"
#include"k3menu.h"
extern uint16_t GameWndH;
static inline void immdraw_fill_rect(int16_t x, int16_t y, int16_t w, int16_t h, float r, float g, float b, float a, float borderRadius) {
k3BatchAdd(NULL, (struct k3RectF) {0, 0, 1, 1}, (struct k3RectF) {
x, GameWndH - y - h,
w, h
}, 0, (vec4) {r, g, b, a}, borderRadius, 0);
}
static inline void immdraw_font_draw(struct k3Font *font, int16_t x, int16_t y, int16_t w, float sz, size_t len, const char *txt, int alignment, float r, float g, float b, float a) {
k3FontDraw(font, x, GameWndH - y - sz, sz, txt, w, alignment, (vec4) {r, g, b, a});
}
static inline void immdraw_font_size(struct k3Font *font, float sz, const char *txt, int16_t wall, int16_t aabb[2]) {
struct k3RectF txtsz;
k3FontSz(font, sz, txt, wall, &txtsz);
aabb[0] = ceilf(txtsz.w);
aabb[1] = ceilf(txtsz.h);
}
static inline void immdraw_image_draw(k3MImageData *data, int16_t x, int16_t y, int16_t w, int16_t h, float r, float g, float b, float a) {
struct k3Tex *tex = *data;
k3BatchAdd(tex, (struct k3RectF) {0, 0, 1, 1}, (struct k3RectF) {x, GameWndH - y - h, w, h}, 0, (vec4) {r, g, b, a}, 0, 0);
}
static int16_t crop_aabb[4] = {-1, -1, -1, -1};
static inline void immdraw_crop_get(int16_t aabb[4]) {
memcpy(aabb, crop_aabb, sizeof(crop_aabb));
}
static inline void immdraw_crop_set(int16_t aabb[4]) {
memcpy(crop_aabb, aabb, sizeof(crop_aabb));
if(aabb[0] == -1 && aabb[1] == -1 && aabb[2] == -1 && aabb[3] == -1) {
glDisable(GL_SCISSOR_TEST);
} else {
glEnable(GL_SCISSOR_TEST);
glScissor(aabb[0], GameWndH - aabb[3], aabb[2] - aabb[0], aabb[3] - aabb[1]);
}
}
static inline void immdraw_crop_add(int16_t aabb[4]) {
int16_t newaabb[4];
memcpy(newaabb, crop_aabb, sizeof(newaabb));
if(aabb[0] > newaabb[0] || newaabb[0] == -1) {
newaabb[0] = aabb[0];
}
if(aabb[1] > newaabb[1] || newaabb[1] == -1) {
newaabb[1] = aabb[1];
}
if(aabb[2] < newaabb[2] || newaabb[2] == -1) {
newaabb[2] = aabb[2];
}
if(aabb[3] < newaabb[3] || newaabb[3] == -1) {
newaabb[3] = aabb[3];
}
immdraw_crop_set(newaabb);
}

811
src/k3.c

File diff suppressed because it is too large Load Diff

View File

@@ -5,12 +5,13 @@
#include<cglm/vec3.h> #include<cglm/vec3.h>
#include<cglm/mat4.h> #include<cglm/mat4.h>
#include<cglm/quat.h> #include<cglm/quat.h>
#include"k3anim.h"
#ifdef k3_MULTITHREAD #ifdef k3_MULTITHREAD
#include<pthread.h> #include<pthread.h>
#endif #endif
void k3Init(); void k3Init(bool tc, bool mipmap);
void k3Resize(uint16_t width, uint16_t height); void k3Resize(uint16_t width, uint16_t height);
void k3Update(); void k3Update();
@@ -21,9 +22,18 @@ enum k3TexType {
k3_RAWCOLOR, k3_RAWCOLOR,
}; };
enum k3TexParam {
k3_TEX_SET_MIPMAP_ALPHA,
k3_TEX_SET_MIPMAPPING_DISABLED,
};
#define k3_PARAM_STANDARD 0
#define k3_PARAM_MAX_DOWNSAMPLE 1
struct k3Tex; struct k3Tex;
struct k3Tex *k3TexCreate(enum k3TexType type); struct k3Tex *k3TexCreate(enum k3TexType type);
int k3TexSetParameter(struct k3Tex*, enum k3TexParam, void*);
void k3TexUpdate(struct k3Tex*, enum k3TexType type, int index, uint16_t width, uint16_t height, void *data); void k3TexUpdate(struct k3Tex*, enum k3TexType type, int index, uint16_t width, uint16_t height, void *data);
void k3TexUpdateSub(struct k3Tex*, int index, uint16_t x, uint16_t y, uint16_t width, uint16_t height, void *data);
uint32_t k3TexSzX(struct k3Tex*); uint32_t k3TexSzX(struct k3Tex*);
uint32_t k3TexSzY(struct k3Tex*); uint32_t k3TexSzY(struct k3Tex*);
uint32_t k3TexSzZ(struct k3Tex*); uint32_t k3TexSzZ(struct k3Tex*);
@@ -39,7 +49,7 @@ struct k3GLSLG *k3ShaderGLSLG(const char *src_, const char*(*ldr)(const char *fn
struct k3GLSLP; struct k3GLSLP;
struct k3GLSLP *k3ProgramGLSL(struct k3GLSLV*, struct k3GLSLF*, struct k3GLSLG*); struct k3GLSLP *k3ProgramGLSL(struct k3GLSLV*, struct k3GLSLF*, struct k3GLSLG*);
uint16_t k3ProgramGetUId(struct k3GLSLP*, const char *key); int16_t k3ProgramGetUId(struct k3GLSLP*, const char *key);
struct k3ARBVP; struct k3ARBVP;
struct k3ARBVP *k3ProgramARBVP(const char *src); struct k3ARBVP *k3ProgramARBVP(const char *src);
@@ -67,7 +77,7 @@ struct k3Mat {
} arbfp; } arbfp;
struct { struct {
struct k3GLSLP *hp; struct k3GLSLP *hp;
#ifdef k3_IRREGULAR_SHADOWS #ifdef k3_IRREGULAR_SHADOWS
struct k3GLSLP *hpIrreg1; struct k3GLSLP *hpIrreg1;
struct k3GLSLP *hpIrreg2; struct k3GLSLP *hpIrreg2;
@@ -96,32 +106,22 @@ struct k3Mat {
char additive; char additive;
char transparent; char transparent;
char nocull; char nocull;
char alphatest; float alphatest;
char depthwrite; char depthwrite;
} passes[1]; } passes[1];
}; };
struct k3AnimationBone {
vec4 translation;
versor rotation;
};
struct k3Animation {
struct k3AnimationBone *frames;
mat4 *invBind;
uint8_t *boneParents;
uint16_t fps;
uint16_t frameCount;
uint16_t boneCount;
uint16_t id;
};
struct k3Animator { struct k3Animator {
float time; struct k3Mdl *mdl;
int loop;
mat4 *inter; struct k3AnimationBone *bones;
struct k3Animation *base; struct k3Animation *anim;
double time;
bool playing;
double playStartTime;
}; };
struct k3Offscreen; struct k3Offscreen;
@@ -155,7 +155,7 @@ struct k3Light {
}; };
struct k3Mesh { struct k3Mesh {
uint16_t idxStart, idxNumber; uint32_t idxStart, idxNumber;
struct k3Mat mat; struct k3Mat mat;
}; };
@@ -168,16 +168,19 @@ void k3StorageUnref(struct k3Storage*);
struct k3Mdl; struct k3Mdl;
struct k3Mdl *k3MdlCreate(size_t verts, size_t indices, size_t boneCount, vec3 *pos, uint8_t *nrm, float *uvs, uint8_t *cols, uint8_t *boneids, uint16_t *boneweights, uint16_t *inds, mat4 *invBind, uint8_t *boneParents); struct k3Mdl *k3MdlCreate(size_t verts, size_t indices, size_t boneCount, vec3 *pos, uint8_t *nrm, float *uvs, uint8_t *cols, uint8_t *boneids, uint16_t *boneweights, uint16_t *inds, mat4 *invBind, uint8_t *boneParents);
void k3MdlUpdatePos(struct k3Mdl *mdl, vec3 *pos); void k3MdlUpdatePos(struct k3Mdl *mdl, vec3 *pos);
void k3MdlAddMesh(struct k3Mdl*, struct k3Mat*, uint16_t idxStart, uint16_t idxNumber); void k3MdlUpdateNrm(struct k3Mdl *mdl, uint8_t *nrm);
void k3MdlAddMesh(struct k3Mdl*, struct k3Mat*, uint32_t idxStart, uint32_t idxNumber);
struct k3Mesh *k3MdlGetMeshes(struct k3Mdl*, size_t *count); struct k3Mesh *k3MdlGetMeshes(struct k3Mdl*, size_t *count);
void k3MdlAddAnim(struct k3Mdl*, struct k3Animation*); void k3MdlAddAnim(struct k3Mdl*, struct k3AnimationFountain*);
struct k3Animation *k3MdlGetAnim(struct k3Mdl*, uint16_t id); struct k3AnimationFountain *k3MdlGetAnim(struct k3Mdl*, uint16_t id);
size_t k3MdlGetBoneCount(struct k3Mdl*); size_t k3MdlGetBoneCount(struct k3Mdl*);
void k3MdlSetDebugName(struct k3Mdl*, const char*);
struct k3Mdl *k3MdlCopySubs(struct k3Mdl*); struct k3Mdl *k3MdlCopySubs(struct k3Mdl*);
void k3MdlSetDebugName(struct k3Mdl*, const char*);
void k3MdlQueryWeights(struct k3Mdl*, size_t bone, float *output);
intmax_t k3MdlGetBoneFromName(struct k3Mdl*, const char*);
void k3MdlSetBoneNames(struct k3Mdl*, const char*);
void k3AnimatorInit(struct k3Animator*, struct k3Mdl*);
void k3AnimatorSet(struct k3Animator*, float); void k3AnimatorSet(struct k3Animator*, float);
void k3AnimatorStep(struct k3Animator*, float); void k3AnimatorStep(struct k3Animator*, float);
@@ -192,7 +195,7 @@ void k3BatchClear();
void k3PassForward(mat4 projection, mat4 cam); void k3PassForward(mat4 projection, mat4 cam);
void k3PassDepthOnly(mat4 projection, mat4 cam, int clear, int cull); void k3PassDepthOnly(mat4 projection, mat4 cam, int clear, int cull);
void k3PassShadowmap(mat4 projection, mat4 cam, struct k3Offscreen *offscr); void k3PassShadowmap(mat4 projection, mat4 cam, struct k3Offscreen *offscr, int minimumSubdivision);
struct k3Offscreen; struct k3Offscreen;
struct k3Offscreen *k3OffscreenCreateMultisampled(struct k3Tex *diffuse, struct k3Tex *depth, uint8_t samples); struct k3Offscreen *k3OffscreenCreateMultisampled(struct k3Tex *diffuse, struct k3Tex *depth, uint8_t samples);
@@ -212,7 +215,7 @@ int k3CubemapTraditional(struct k3Tex*, mat4 proj, mat4 cam);
void k3SetTime(float t); void k3SetTime(float t);
enum k3LogLevel { enum k3LogLevel {
k3_DEBUG, k3_INFO, k3_WARN, k3_ERR k3_TRACE, k3_DEBUG, k3_INFO, k3_WARN, k3_ERR
}; };
typedef void(*k3LogCallback)(enum k3LogLevel, const char *str, size_t len); typedef void(*k3LogCallback)(enum k3LogLevel, const char *str, size_t len);
void k3SetLogCallback(k3LogCallback); void k3SetLogCallback(k3LogCallback);

View File

@@ -7,12 +7,15 @@
#include<cglm/vec2.h> #include<cglm/vec2.h>
#include<cglm/frustum.h> #include<cglm/frustum.h>
#include<cglm/cam.h> #include<cglm/cam.h>
#include<string.h>
#define GL_FROM_K3TEX(k3t) ((k3t) ? (k3t)->tex : 0) #define GL_FROM_K3TEX(k3t) ((k3t) ? (k3t)->tex : 0)
#define GL_FROM_K3MARCHER(k3m) ((GLuint) (uintptr_t) (k3m)) #define GL_FROM_K3MARCHER(k3m) ((GLuint) (uintptr_t) (k3m))
#define GL_FROM_K3ARBVP(k3m) ((GLuint) (uintptr_t) (k3m)) #define GL_FROM_K3ARBVP(k3m) ((GLuint) (uintptr_t) (k3m))
#define GL_FROM_K3ARBFP(k3m) ((GLuint) (uintptr_t) (k3m)) #define GL_FROM_K3ARBFP(k3m) ((GLuint) (uintptr_t) (k3m))
#define GL_FROM_K3GLSL(k3m) ((GLuint) (uintptr_t) (k3m)) #define GL_FROM_K3GLSL(k3m) (((struct k3GLSLP*) k3m)->handle)
extern bool k3IsCore;
struct k3Tex { struct k3Tex {
GLuint tex; GLuint tex;
@@ -25,10 +28,8 @@ struct k3Tex {
GLuint glInType; GLuint glInType;
bool mipmap; bool mipmap;
// Asynchronous decoding bool mipmapForceDisabled;
uint8_t deferredRemaining; int mipmapAlphaMode;
uint8_t *deferredData[6];
struct k3Tex *deferredNext;
}; };
struct k3Storage { struct k3Storage {
@@ -71,9 +72,10 @@ struct k3Mdl {
size_t boneCount; size_t boneCount;
mat4 *invBind; mat4 *invBind;
uint8_t *boneParents; uint8_t *boneParents;
char *boneNames;
uint16_t animCount; uint16_t animCount;
struct k3Animation **anims; struct k3AnimationFountain **anims;
vec3 aabb[2]; vec3 aabb[2];
@@ -81,3 +83,52 @@ struct k3Mdl {
const char *debugname; const char *debugname;
}; };
struct k3GLSLP {
GLhandleARB handle;
size_t ucount;
char **uname;
GLint *uloc;
};
struct k3Timer {
GLuint qStart;
GLuint qEnd;
char name[64];
};
extern struct k3Timer *k3Timers;
extern size_t k3TimerCount;
static inline struct k3Timer k3StartTimer(char *name) {
struct k3Timer t = {};
if(!GLAD_GL_ARB_timer_query) {
return t;
}
glGenQueries(2, (GLuint*) &t);
strncpy(t.name, name, sizeof(t.name));
glQueryCounter(t.qStart, GL_TIMESTAMP);
if(GLAD_GL_KHR_debug) {
glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 0, -1, name);
}
return t;
}
static inline void k3EndTimer(struct k3Timer t) {
if(!GLAD_GL_ARB_timer_query) {
return;
}
glQueryCounter(t.qEnd, GL_TIMESTAMP);
if(GLAD_GL_KHR_debug) {
glPopDebugGroup();
}
k3Timers = realloc(k3Timers, sizeof(*k3Timers) * (k3TimerCount + 1));
k3Timers[k3TimerCount++] = t;
}

185
src/k3anim.c Normal file
View File

@@ -0,0 +1,185 @@
#include"k3anim.h"
#include<cglm/mat4.h>
#include<cglm/quat.h>
#include<stdint.h>
#include<stddef.h>
static uint32_t lcg() {
static uint32_t asdf = 0;
asdf = asdf * 1664525 + 1013904223;
return asdf;
}
static void power_iter(mat4 M, int iterations, float eps, vec4 ev) {
// Random starting vector
uint32_t rng = lcg();
ev[0] = (rng & 0xFF) / 255.f;
rng >>= 8;
ev[1] = (rng & 0xFF) / 255.f;
rng >>= 8;
ev[2] = (rng & 0xFF) / 255.f;
rng >>= 8;
ev[3] = rng / 255.f;
for(int i = 0; i < iterations; i++) {
vec4 k;
glm_mat4_mulv(M, ev, k);
glm_vec4_normalize_to(k, ev);
}
}
static void quat_to_mat(versor v, mat4 m) {
for(int y = 0; y < 4; y++) {
for(int x = 0; x < 4; x++) {
m[y][x] = v[y] * v[x];
}
}
}
bool k3AnimationUpdate(struct k3Animation *anim, float time) {
if(anim->op == k3_ANIM_BASIC) {
struct k3AnimationBasic *basic = (void*) anim;
struct k3AnimationFountain *fountain = basic->fountain;
if(!anim->cache) {
anim->bones = fountain->bones;
anim->cache = _mm_malloc(sizeof(*anim->cache) * anim->bones, 16);
}
float frame = time * fountain->fps;
if(!basic->loop) {
frame = fminf(frame, fountain->frameCount - 1);
}
size_t f0 = floorf(frame);
size_t f1 = (f0 + 1);
float alpha = frame - f0;
f0 %= fountain->frameCount;
f1 %= fountain->frameCount;
for(size_t b = 0; b < fountain->bones; b++) {
vec4 t0, t1;
glm_vec4_copy(fountain->frames[fountain->bones * f0 + b].translation, t0);
glm_vec4_copy(fountain->frames[fountain->bones * f1 + b].translation, t1);
versor r0, r1;
glm_quat_copy(fountain->frames[fountain->bones * f0 + b].rotation, r0);
glm_quat_copy(fountain->frames[fountain->bones * f1 + b].rotation, r1);
glm_vec4_lerp(t0, t1, alpha, basic->cache[b].translation);
glm_quat_slerp(r0, r1, alpha, basic->cache[b].rotation);
}
return true;
} else if(anim->op == k3_ANIM_BLEND) {
struct k3AnimationBlend *blend = (void*) anim;
if(blend->children == 0) return false;
if(!blend->cache || blend->bones != blend->subs[0]->bones) {
if(blend->cache) _mm_free(blend->cache);
blend->bones = blend->subs[0]->bones;
blend->cache = _mm_malloc(blend->bones * sizeof(*blend->cache), 16);
}
for(size_t c = 0; c < blend->children; c++) {
if(!k3AnimationUpdate(blend->subs[c], blend->offsets[c] + time * blend->speeds[c])) return false;
if(blend->subs[c]->bones != blend->bones) return false;
}
for(size_t b = 0; b < blend->bones; b++) {
float weightSum = 0;
vec4 translationSum = GLM_VEC4_ZERO_INIT;
mat4 rotationSum = GLM_MAT4_ZERO_INIT;
for(size_t c = 0; c < blend->children; c++) {
float w = blend->weights[c * blend->bones + b];
weightSum += w;
glm_vec4_muladds(blend->subs[c]->cache[b].translation, w, translationSum);
mat4 rm;
quat_to_mat(blend->subs[c]->cache[b].rotation, rm);
glm_mat4_scale(rm, w);
glm_vec4_add(rotationSum[0], rm[0], rotationSum[0]);
glm_vec4_add(rotationSum[1], rm[1], rotationSum[1]);
glm_vec4_add(rotationSum[2], rm[2], rotationSum[2]);
glm_vec4_add(rotationSum[3], rm[3], rotationSum[3]);
}
glm_vec4_scale(translationSum, 1.0f / weightSum, blend->cache[b].translation);
vec4 dominantEigenVector;
power_iter(rotationSum, 64, 1e-5f, dominantEigenVector);
glm_vec4_normalize(dominantEigenVector);
glm_quat_copy((float*) dominantEigenVector, blend->cache[b].rotation);
}
return true;
} else if(anim->op == k3_ANIM_ADD) {
struct k3AnimationAdd *add = (void*) anim;
if(!k3AnimationUpdate(add->sub1, time)) return false;
if(!k3AnimationUpdate(add->sub2, time)) return false;
if(!add->cache || add->bones != add->sub1->bones) {
if(add->cache) _mm_free(add->cache);
add->bones = add->sub1->bones;
add->cache = _mm_malloc(add->bones * sizeof(*add->cache), 16);
}
versor norot = {0, 0, 0, 1};
for(size_t b = 0; b < add->bones; b++) {
glm_vec4_copy(add->sub1->cache[b].translation, add->cache[b].translation);
glm_vec4_muladds(add->sub2->cache[b].translation, add->scale, add->cache[b].translation);
versor o;
glm_quat_nlerp(norot, add->sub2->cache[b].rotation, add->scale, o);
glm_quat_mul(o, add->sub1->cache[b].rotation, add->cache[b].rotation);
}
return true;
}
return false;
}
void k3AnimationFree(struct k3Animation *anim) {
if(!anim) return;
if(anim->ref != 0) {
anim->ref--;
return;
}
if(anim->op == k3_ANIM_BLEND) {
struct k3AnimationBlend *blend = (void*) anim;
for(size_t c = 0; c < blend->children; c++) {
k3AnimationFree(blend->subs[c]);
}
free(blend->subs);
free(blend->weights);
free(blend->offsets);
free(blend->speeds);
} else if(anim->op == k3_ANIM_ADD) {
struct k3AnimationAdd *add = (void*) anim;
k3AnimationFree(add->sub1);
k3AnimationFree(add->sub2);
}
_mm_free(anim->cache);
free(anim);
}

70
src/k3anim.h Normal file
View File

@@ -0,0 +1,70 @@
#pragma once
#include<cglm/mat4.h>
#include<cglm/quat.h>
#include<stdint.h>
#include<stddef.h>
struct k3AnimationBone {
vec4 translation;
versor rotation;
};
enum k3AnimationOp {
k3_ANIM_BASIC,
k3_ANIM_BLEND,
k3_ANIM_ADD,
};
struct k3AnimationFountain {
float fps;
size_t frameCount;
size_t bones;
uint16_t id;
_Alignas(16) struct k3AnimationBone frames[];
};
struct k3Animation {
enum k3AnimationOp op;
size_t bones;
struct k3AnimationBone *cache;
size_t ref;
};
struct k3AnimationBasic {
enum k3AnimationOp op;
size_t bones;
struct k3AnimationBone *cache;
size_t ref;
struct k3AnimationFountain *fountain;
bool loop;
};
struct k3AnimationBlend {
enum k3AnimationOp op;
size_t bones;
struct k3AnimationBone *cache;
size_t ref;
size_t children;
float *offsets;
float *speeds;
float *weights;
struct k3Animation **subs;
};
struct k3AnimationAdd {
enum k3AnimationOp op;
size_t bones;
struct k3AnimationBone *cache;
size_t ref;
struct k3Animation *sub1;
struct k3Animation *sub2;
float scale;
};
bool k3AnimationUpdate(struct k3Animation *anim, float time);
void k3AnimationFree(struct k3Animation *anim);

View File

@@ -2,6 +2,7 @@
#include"gl.h" #include"gl.h"
#include<string.h> #include<string.h>
#include"k3_internal.h"
struct S { struct S {
struct k3RectF src; struct k3RectF src;
@@ -13,12 +14,17 @@ struct S {
static struct k3Tex *activeTex; static struct k3Tex *activeTex;
static float activeBorderRadius; static float activeBorderRadius;
static float activeMinAlpha;
static size_t SCount, SCapacity; static size_t SCount, SCapacity;
static struct S *S; static struct S *S;
static struct k3GLSLP *coreProg; static struct k3GLSLP *coreProg;
static GLuint coreVBO; static GLuint coreVBO;
static GLuint coreUResolution;
static float ResolutionX;
static float ResolutionY;
void k3BatchInit() { void k3BatchInit() {
if(k3IsCore) { if(k3IsCore) {
@@ -31,17 +37,19 @@ void k3BatchInit() {
"out vec2 v_uv;\n" "out vec2 v_uv;\n"
"out vec4 v_color;\n" "out vec4 v_color;\n"
"out vec2 v_size;\n" "out vec2 v_size;\n"
"uniform vec2 u_resolution;\n"
"void main() {\n" "void main() {\n"
" v_uv = a_uv;\n" " v_uv = a_uv;\n"
" v_color = a_color;\n" " v_color = a_color;\n"
" v_size = a_size;\n" " v_size = a_size;\n"
" gl_Position = vec4(vec2(a_pos.x / 3600.0, a_pos.y / 2025.0) * 2.0 - 1.0, 0.0, 1.0);\n" " gl_Position = vec4(vec2(a_pos.x / u_resolution.x, a_pos.y / u_resolution.y) * 2.0 - 1.0, 0.0, 1.0);\n"
"}\n" "}\n"
, NULL), k3ShaderGLSLF( , NULL), k3ShaderGLSLF(
"#version 330\n" "#version 330\n"
"uniform sampler2D u_tex;\n" "uniform sampler2D u_tex;\n"
"uniform float u_texuse;\n" "uniform float u_texuse;\n"
"uniform float u_borderradius;\n" "uniform float u_borderradius;\n"
"uniform float u_minalpha;\n"
"in vec2 v_uv;\n" "in vec2 v_uv;\n"
"in vec4 v_color;\n" "in vec4 v_color;\n"
"in vec2 v_size;\n" "in vec2 v_size;\n"
@@ -54,19 +62,26 @@ void k3BatchInit() {
" if(length(c) > u_borderradius) {\n" " if(length(c) > u_borderradius) {\n"
" discard;\n" " discard;\n"
" }\n" " }\n"
" fragcolor = mix(vec4(1, 1, 1, 1), texture2D(u_tex, v_uv), u_texuse) * v_color;\n" " vec4 col = texture2D(u_tex, v_uv);\n"
" if(u_minalpha > 0.0) {\n"
" col.a = clamp((col.a - u_minalpha + 0.1) / 0.1, 0.0, 1.0);\n"
" }\n"
" fragcolor = mix(vec4(1, 1, 1, 1), col, u_texuse) * v_color;\n"
"}\n" "}\n"
, NULL), NULL); , NULL), NULL);
coreUResolution = k3ProgramGetUId(coreProg, "u_resolution");
glGenBuffers(1, &coreVBO); glGenBuffers(1, &coreVBO);
} }
} }
void k3BatchAdd(struct k3Tex *tex, struct k3RectF src, struct k3RectF dst, float rot, vec4 color, float borderRadius) { void k3BatchAdd(struct k3Tex *tex, struct k3RectF src, struct k3RectF dst, float rot, vec4 color, float borderRadius, float minAlpha) {
if(activeTex != tex || borderRadius != activeBorderRadius) { if(activeTex != tex || borderRadius != activeBorderRadius || minAlpha != activeMinAlpha) {
k3BatchFlush(); k3BatchFlush();
activeTex = tex; activeTex = tex;
activeBorderRadius = borderRadius; activeBorderRadius = borderRadius;
activeMinAlpha = minAlpha;
} }
if(SCount == SCapacity) { if(SCount == SCapacity) {
@@ -101,6 +116,7 @@ void k3BatchFlush() {
} }
glDisable(GL_DEPTH_TEST); glDisable(GL_DEPTH_TEST);
glDisable(GL_CULL_FACE);
if(GLAD_GL_ARB_vertex_program) { if(GLAD_GL_ARB_vertex_program) {
glDisable(GL_VERTEX_PROGRAM_ARB); glDisable(GL_VERTEX_PROGRAM_ARB);
@@ -109,9 +125,6 @@ void k3BatchFlush() {
glDisable(GL_FRAGMENT_PROGRAM_ARB); glDisable(GL_FRAGMENT_PROGRAM_ARB);
} }
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glActiveTexture(GL_TEXTURE0); glActiveTexture(GL_TEXTURE0);
if(activeTex) { if(activeTex) {
@@ -123,7 +136,13 @@ void k3BatchFlush() {
} }
if(k3IsCore) { if(k3IsCore) {
glUseProgram((GLuint) coreProg); glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
GLuint handle = GL_FROM_K3GLSL(coreProg);
glUseProgram(handle);
glUniform2f(coreUResolution, ResolutionX, ResolutionY);
float *farr = alloca(SCount * 60 * sizeof(*farr)); float *farr = alloca(SCount * 60 * sizeof(*farr));
@@ -201,14 +220,14 @@ void k3BatchFlush() {
glBindBufferARB(GL_ARRAY_BUFFER_ARB, coreVBO); glBindBufferARB(GL_ARRAY_BUFFER_ARB, coreVBO);
glBufferDataARB(GL_ARRAY_BUFFER_ARB, SCount * 60 * sizeof(*farr), farr, GL_DYNAMIC_DRAW); glBufferDataARB(GL_ARRAY_BUFFER_ARB, SCount * 60 * sizeof(*farr), farr, GL_DYNAMIC_DRAW);
glUniform1f(glGetUniformLocation((GLuint) coreProg, "u_texuse"), !!activeTex); glUniform1f(k3ProgramGetUId(coreProg, "u_texuse"), !!activeTex);
glUniform1f(k3ProgramGetUId(coreProg, "u_borderradius"), activeBorderRadius);
glUniform1f(k3ProgramGetUId(coreProg, "u_minalpha"), activeMinAlpha);
glUniform1f(glGetUniformLocation((GLuint) coreProg, "u_borderradius"), activeBorderRadius); GLint aPos = glGetAttribLocation(handle, "a_pos");
GLint aUv = glGetAttribLocation(handle, "a_uv");
GLint aPos = glGetAttribLocation((GLuint) coreProg, "a_pos"); GLint aColor = glGetAttribLocation(handle, "a_color");
GLint aUv = glGetAttribLocation((GLuint) coreProg, "a_uv"); GLint aSize = glGetAttribLocation(handle, "a_size");
GLint aColor = glGetAttribLocation((GLuint) coreProg, "a_color");
GLint aSize = glGetAttribLocation((GLuint) coreProg, "a_size");
glEnableVertexAttribArray(aPos); glEnableVertexAttribArray(aPos);
glEnableVertexAttribArray(aUv); glEnableVertexAttribArray(aUv);
@@ -231,6 +250,16 @@ void k3BatchFlush() {
glUseProgramObjectARB(0); glUseProgramObjectARB(0);
} }
if(activeMinAlpha) {
glDisable(GL_BLEND);
} else {
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
glEnable(GL_ALPHA_TEST);
glAlphaFunc(GL_GREATER, activeMinAlpha);
glBegin(GL_QUADS); glBegin(GL_QUADS);
struct S *s = S; struct S *s = S;
for(size_t i = 0; i < SCount; i++) { for(size_t i = 0; i < SCount; i++) {
@@ -252,8 +281,23 @@ void k3BatchFlush() {
} }
glEnd(); glEnd();
glDisable(GL_ALPHA_TEST);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
} }
SCount = 0; SCount = 0;
} }
void k3BatchSetResolution(float w, float h) {
ResolutionX = w;
ResolutionY = h;
if(!k3IsCore) {
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, ResolutionX, 0, ResolutionY, -1, 1);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
}

View File

@@ -10,5 +10,9 @@ struct k3RectF {
float h; float h;
}; };
void k3BatchAdd(struct k3Tex *tex, struct k3RectF src, struct k3RectF dst, float rot, vec4 color, float borderRadius); void k3BatchInit();
void k3BatchAdd(struct k3Tex *tex, struct k3RectF src, struct k3RectF dst, float rot, vec4 color, float borderRadius, float minAlpha);
void k3BatchFlush(); void k3BatchFlush();
void k3BatchSetResolution(float w, float h);

View File

@@ -3,72 +3,64 @@
#include"k3batch.h" #include"k3batch.h"
#include<string.h> #include<string.h>
#include"gl.h" #include"gl.h"
#include<freetype/freetype.h>
#include<freetype/ftmodapi.h>
#define GLCA_IMPLEMENTATION
#include"glyphcache/glca.h"
#include"k3_internal.h"
static _Thread_local FT_Library ftlib;
struct k3Font *k3FontCreate() { struct k3Font *k3FontCreate() {
if(!ftlib) {
assert(FT_Init_FreeType(&ftlib) == 0);
FT_Property_Set(ftlib, "sdf", "spread", &(int) {64});
}
struct k3Font *ret = calloc(sizeof(*ret), 1); struct k3Font *ret = calloc(sizeof(*ret), 1);
return ret; return ret;
} }
static int cmpglyph(const void *a, const void *b) { void my_set_image(struct GlyphCache *gc, int x, int y, int w, int h, const void *buf) {
return *(const uint32_t*) a - *(const uint32_t*) b; struct k3Font *this = gc->userdata;
for(size_t row = 0; row < h; row++) {
memcpy(&this->atlasCPU[this->atlasSize * (y + row) + x], buf + w * row, w);
}
//k3TexUpdateSub(this->atlas, 0, x, y, w, h, buf);
k3TexUpdate(this->atlas, k3_ALPHA, 0, this->atlasSize, this->atlasSize, this->atlasCPU);
} }
void my_get_image(struct GlyphCache *gc, int x, int y, int w, int h, void *buf) {
int k3FontLoad(struct k3Font *this, const uint8_t *buf, size_t len, k3FontTexLoader texldr) { struct k3Font *this = gc->userdata;
if(*(uint32_t*) buf != 0x03464D42) {
return 0; for(size_t row = 0; row < h; row++) {
memcpy(buf + w * row, &this->atlasCPU[this->atlasSize * (y + row)], w);
} }
}
void my_fill_custom_data(struct GlyphCache *gc, FT_GlyphSlot slot, GlyphCacheGlyph *glyph) {
glyph->data.xAdvance = slot->metrics.horiAdvance / 64.f;
glyph->data.xOffset = slot->metrics.horiBearingX / 64.f;
glyph->data.yOffset = slot->metrics.horiBearingY / 64.f;
}
int k3FontLoad(struct k3Font *this, const char *fn) {
this->atlasSize = k3TexSzMax() / 4;
this->atlas = k3TexCreate(k3_ALPHA);
this->atlasCPU = malloc(this->atlasSize * this->atlasSize);
const uint8_t *end = buf + len; k3TexSetParameter(this->atlas, k3_TEX_SET_MIPMAPPING_DISABLED, &(bool) {true});
buf += 4; k3TexUpdate(this->atlas, k3_ALPHA, 0, this->atlasSize, this->atlasSize, NULL);
uint16_t pages = 0; this->fontPixelSize = this->atlasSize / 16;
while(buf + 5 < end) { assert(FT_New_Face(ftlib, fn, 0, &this->ftface) == 0);
uint8_t blockType = *buf; FT_Select_Charmap(this->ftface, FT_ENCODING_UNICODE);
uint32_t blockSize = *(uint32_t*) (buf + 1); FT_Set_Pixel_Sizes(this->ftface, this->fontPixelSize, 0);
buf += 5;
glca_init(&this->glca, this->ftface, this->atlasSize, this->atlasSize, this, my_set_image, my_get_image, my_fill_custom_data);
if(blockType == 1) { //Info block
buf += 14;
while(*buf) buf++;
buf++;
} else if(blockType == 2) { //Common block
this->lineScale = 1.f / *(uint16_t*) buf;
buf += 2;
this->baseline = *(uint16_t*) buf;
buf += 2;
this->texW = *(uint16_t*) buf;
buf += 2;
this->texH = *(uint16_t*) buf;
buf += 2;
pages = *(uint16_t*) buf;
buf += 7;
} else if(blockType == 3) { //Pages block
if(pages == 0) return 0;
this->pageCount = pages;
this->pages = malloc(sizeof(*this->pages) * this->pageCount);
for(size_t i = 0; i < this->pageCount; i++) {
this->pages[i] = texldr(this, buf);
buf += strlen(buf) + 1;
}
} else if(blockType == 4) { //Chars block
size_t num = blockSize / 20;
this->glyphs = calloc(sizeof(*this->glyphs), this->glyphCount = num);
memcpy(this->glyphs, buf, num * 20);
qsort(this->glyphs, num, sizeof(*this->glyphs), cmpglyph);
buf += blockSize;
} else if(blockType == 5) { //Kerning block
// Ignore kerning for now
buf += blockSize;
}
}
return 1; return 1;
} }
@@ -97,55 +89,117 @@ static uint32_t read_utf8(const char **txt_) {
} }
} }
void k3FontSz(struct k3Font *this, float sz, const char *txt, struct k3RectF *ret) { void k3FontSz(struct k3Font *this, float sz, const char *txt, float wall, struct k3RectF *ret) {
float x = 0, y = 0; if(wall < 0) {
while(1) { wall = HUGE_VALF;
uint32_t cp = read_utf8(&txt);
if(!cp) break;
struct k3FontGlyph *g = k3FontGetGlyph(this, cp);
if(!g) continue;
x += g->xadvance * this->lineScale * sz;
} }
ret->w = x;
} float maxX = 0;
void k3FontDraw(struct k3Font *this, float xStart, float yStart, float sz, const char *txt, vec4 color) {
float x = xStart, y = yStart; float x = 0, y = sz;
while(1) { while(1) {
uint32_t cp = read_utf8(&txt); uint32_t cp = read_utf8(&txt);
if(!cp) break; if(cp == 0) {
break;
if(cp == 10) {
x = xStart;
y -= sz;
} }
struct k3FontGlyph *g = k3FontGetGlyph(this, cp); if(cp == 10) {
x = 0;
y += sz;
}
struct GlyphCacheGlyph *g = glca_request(&this->glca, cp);
if(!g) continue; if(!g) continue;
struct k3Tex *tex = this->pages[g->page]; if(x + sz * g->w / this->fontPixelSize > wall) {
size_t texW = this->texW; x = 0;
size_t texH = this->texH; y += sz;
}
k3BatchAdd(tex, x += sz * g->data.xAdvance / this->fontPixelSize;
(struct k3RectF) {(float) g->x / texW, (float) g->y / texH, (float) g->width / texW, (float) g->height / texH}, maxX = fmaxf(maxX, x);
(struct k3RectF) { }
x + g->xoffset * this->lineScale * sz,
y + ((-g->height - g->yoffset) * this->lineScale + 1) * sz, ret->w = maxX;
g->width * this->lineScale * sz, ret->h = y;
g->height * this->lineScale * sz }
}, 0, color, 0); void k3FontDraw(struct k3Font *this, float xStart, float yStart, float sz, const char *txt, float wall, int alignment, vec4 color) {
if(wall < 0) {
wall = HUGE_VALF;
}
float y = yStart;
while(1) {
size_t lineLength = 0;
x += g->xadvance * this->lineScale * sz; float lineWidth = 0;
uint32_t cp2;
const char *txt2 = txt;
while(1) {
cp2 = read_utf8(&txt2);
if(cp2 == 0 || cp2 == 10) {
break;
}
struct GlyphCacheGlyph *g = glca_request(&this->glca, cp2);
if(g) {
if(lineWidth + sz * g->w / this->fontPixelSize > wall) {
break;
}
}
lineLength++;
if(g) {
lineWidth += sz * g->data.xAdvance / this->fontPixelSize;
}
}
if(lineLength == 0 && cp2 == 0) {
break;
}
float x = 0;
if(alignment == 0) {
x = xStart;
} else if(alignment == 1) {
x = xStart + (wall - lineWidth) / 2;
} else if(alignment == 2) {
x = xStart + wall - lineWidth;
}
for(size_t i = 0; i < lineLength; i++) {
uint32_t cp = read_utf8(&txt);
struct GlyphCacheGlyph *g = glca_request(&this->glca, cp);
if(!g) continue;
struct k3Tex *tex = this->atlas;
size_t texW = k3TexSzX(tex);
size_t texH = k3TexSzY(tex);
k3BatchAdd(tex,
(struct k3RectF) {(float) (g->x + 0.5) / texW, (float) (g->y - 0.5) / texH, (float) (g->w - 1) / texW, (float) (g->h - 1) / texH},
(struct k3RectF) {
x + sz * g->data.xOffset / this->fontPixelSize,
y - sz * ((g->h - g->data.yOffset) / (float) this->fontPixelSize - 0.5f),
sz * g->w / this->fontPixelSize,
sz * g->h / this->fontPixelSize
}, 0, color, 0, k3IsCore ? 0.5 : 0.45);
x += sz * g->data.xAdvance / this->fontPixelSize;
}
// If the line break was caused directly by a LF, skip over it for next line
if(cp2 == 10) {
read_utf8(&txt);
}
y -= sz;
} }
k3BatchFlush(); k3BatchFlush();
} }
struct k3FontGlyph *k3FontGetGlyph(struct k3Font *this, uint32_t cp) {
return bsearch(&cp, this->glyphs, this->glyphCount, sizeof(*this->glyphs), cmpglyph);
}

View File

@@ -4,44 +4,31 @@
#include"k3batch.h" #include"k3batch.h"
#include<string.h> #include<string.h>
struct k3FontGlyph { #define GLCA_CUSTOM_GLYPH_DATA
uint32_t cp; typedef struct GlyphCacheGlyphData {
uint16_t x; float xAdvance;
uint16_t y; float xOffset;
uint16_t width; float yOffset;
uint16_t height; } GlyphCacheGlyphData;
int16_t xoffset; #include"glyphcache/glca.h"
int16_t yoffset;
uint16_t xadvance;
uint8_t page;
uint8_t chnl;
};
struct k3Font { struct k3Font {
void *ud; void *ud;
float lineScale; int atlasSize;
uint8_t *atlasCPU;
struct k3Tex *atlas;
uint16_t baseline; int fontPixelSize;
FT_Face ftface;
uint16_t texW, texH; GlyphCache glca;
size_t glyphCount;
struct k3FontGlyph *glyphs;
size_t pageCount;
struct k3Tex **pages;
}; };
typedef struct k3Tex*(*k3FontTexLoader)(struct k3Font*, const char *name);
struct k3Font *k3FontCreate(); struct k3Font *k3FontCreate();
int k3FontLoad(struct k3Font*, const uint8_t *buf, size_t len, k3FontTexLoader); int k3FontLoad(struct k3Font*, const char *fn);
void k3FontSz(struct k3Font*, float sz, const char *txt, struct k3RectF *ret); void k3FontSz(struct k3Font*, float sz, const char *txt, float wall, struct k3RectF *ret);
void k3FontDraw(struct k3Font*, float x, float y, float sz, const char *txt, vec4 color); void k3FontDraw(struct k3Font*, float x, float y, float sz, const char *txt, float wall, int alignment, vec4 color);
struct k3FontGlyph *k3FontGetGlyph(struct k3Font*, uint32_t cp);
static inline int k3UTF8Encode(uint32_t cp, uint8_t ret[static 4]) { static inline int k3UTF8Encode(uint32_t cp, uint8_t ret[static 4]) {
if(cp < 0x80) { if(cp < 0x80) {
@@ -74,4 +61,4 @@ static inline const char *k3UTF8LastCodepointZ(const char *txt) {
} }
} }
return NULL; return NULL;
} }

View File

@@ -1,8 +1,129 @@
#include"k3menu.h" #include"k3menu.h"
#include"gl.h"
#include<string.h> #include<string.h>
#include<stdio.h> #include<stdio.h>
#include<GLFW/glfw3.h> #include<GLFW/glfw3.h>
#include"immdraw.h"
void k3MMeasure(struct k3MObj *this) {
for(size_t c = 0; c < this->childCount; c++) {
k3MMeasure(this->children[c]);
}
struct k3MEvent newev = {
.code = k3M_EVENT_MEASURE,
.kind = k3M_EVENTKIND_DIRECT,
.original = (void*) this,
.target = (void*) this,
};
if(!k3MEventSend(&newev)) {
this->wDesired = this->w;
this->hDesired = this->h;
}
}
void k3MArrange(struct k3MObj *this) {
struct k3MEvent newev = {
.code = k3M_EVENT_ARRANGE,
.kind = k3M_EVENTKIND_DIRECT,
.original = (void*) this,
.target = (void*) this,
};
k3MEventSend(&newev);
for(size_t c = 0; c < this->childCount; c++) {
k3MArrange(this->children[c]);
}
newev = (struct k3MEvent) {
.code = k3M_EVENT_POST_ARRANGE,
.kind = k3M_EVENTKIND_DIRECT,
.original = (void*) this,
.target = (void*) this,
};
k3MEventSend(&newev);
}
static bool linear_measure(struct k3MEvent *ev, uint8_t *ud) {
struct k3MObj *o = ev->target;
o->wDesired = 0;
o->hDesired = 0;
intmax_t prevChildMargin[4] = {};
for(size_t i = 0; i < o->childCount; i++) {
struct k3MObj *c = o->children[i];
if(o->wDesired < c->wDesired) {
o->wDesired = c->wDesired;
}
intmax_t margin[4] = {};
struct k3MProperty *prop = k3MFindProperty(o, k3M_PROP_MARGIN, true);
if(prop) {
for(int s = 0; s < 4; s++) {
if(prop->units[s] == k3M_UNIT_ABSOLUTE) {
margin[s] = prop->f[s];
} else if(prop->units[s] == k3M_UNIT_PROPORTION && o->parent) {
margin[s] = prop->f[s] * (s % 2 == 0 ? o->h : o->w);
}
}
}
o->hDesired += margin[0] + c->hDesired + margin[2];
}
}
static bool linear_arrange(struct k3MEvent *ev, uint8_t *ud) {
struct k3MObj *o = ev->target;
intmax_t padding[4] = {};
struct k3MProperty *paddingProp = k3MFindProperty(o, k3M_PROP_PADDING, true);
if(paddingProp) {
for(int s = 0; s < 4; s++) {
if(paddingProp->units[s] == k3M_UNIT_ABSOLUTE) {
padding[s] = paddingProp->f[s];
} else if(paddingProp->units[s] == k3M_UNIT_PROPORTION && o->parent) {
padding[s] = paddingProp->f[s] * (s % 2 == 0 ? o->h : o->w);
}
}
}
size_t y = o->y + padding[0];
for(size_t i = 0; i < o->childCount; i++) {
struct k3MObj *c = o->children[i];
c->x = o->x + padding[3];
c->y = y;
struct k3MProperty *prop = k3MFindProperty(c, k3M_PROP_WIDTH, true);
if(prop) {
if(prop->units[0] == k3M_UNIT_ABSOLUTE) {
c->w = prop->f[0];
} else if(prop->units[0] == k3M_UNIT_PROPORTION) {
c->w = o->w * prop->f[0];
}
}
if(c->w > o->w - padding[1] - padding[3]) {
c->w = o->w - padding[1] - padding[3];
}
if(!k3MEventSend(&(struct k3MEvent) {.original = o, .target = c, .code = k3M_EVENT_SET_HEIGHT_FROM_WIDTH})) {
c->h = c->hDesired;
}
y += c->h;
}
}
void k3MSetLayoutLinear(struct k3MObj *this, bool vertical) {
k3MRegisterEventHandler(this, k3M_EVENT_MEASURE, linear_measure, NULL, 0);
k3MRegisterEventHandler(this, k3M_EVENT_ARRANGE, linear_arrange, NULL, 0);
}
void k3MenuSetBounds_(struct k3MObj *this, int16_t x, int16_t y, int16_t w, int16_t h) { void k3MenuSetBounds_(struct k3MObj *this, int16_t x, int16_t y, int16_t w, int16_t h) {
this->x = x; this->x = x;
@@ -38,17 +159,30 @@ int k3MRegisterEventHandler_(struct k3MObj *obj, uint16_t evcode, k3MEventHandle
if(ud) { if(ud) {
memcpy(obj->handlers[obj->handlerCount - 1].ud, ud, udSize); memcpy(obj->handlers[obj->handlerCount - 1].ud, ud, udSize);
} }
return 1;
} }
bool k3MEventSend(struct k3MEvent *ev) { bool k3MEventSend(struct k3MEvent *ev) {
struct k3MObj *target = ev->target; // ev->target can change
int16_t aabb[4];
if(ev->code == k3M_EVENT_DRAW && target->crop) {
immdraw_crop_get(aabb);
immdraw_crop_add(&(int16_t[4]) {target->x, target->y, target->x + target->w, target->y + target->h});
}
bool status;
while(!event_send_nonrecur(ev)) { while(!event_send_nonrecur(ev)) {
if(ev->kind == k3M_EVENTKIND_DIRECT) { if(ev->kind == k3M_EVENTKIND_DIRECT) {
return false; status = false;
goto ret;
} else if(ev->kind == k3M_EVENTKIND_BUBBLE) { } else if(ev->kind == k3M_EVENTKIND_BUBBLE) {
ev->target = ev->target->parent; ev->target = ev->target->parent;
if(ev->target == ev->original || !ev->target) { if(ev->target == ev->original || !ev->target) {
return false; status = false;
goto ret;
} }
} else if(ev->kind == k3M_EVENTKIND_CAPTURE) { } else if(ev->kind == k3M_EVENTKIND_CAPTURE) {
struct k3MObj *parent = ev->target; struct k3MObj *parent = ev->target;
@@ -58,23 +192,32 @@ bool k3MEventSend(struct k3MEvent *ev) {
ev->target = parent->children[i]; ev->target = parent->children[i];
if(k3MEventSend(ev)) { if(k3MEventSend(ev)) {
return true; status = true;
goto ret;
} }
} }
} }
return false; status = false;
goto ret;
} }
} }
return true; status = true;
ret:
if(ev->code == k3M_EVENT_DRAW && target->crop) {
immdraw_crop_set(aabb);
}
return status;
} }
static void obj_draw(struct k3MObj *this) { static void obj_draw_direct(struct k3MObj *this) {
struct k3MProperty *borderRadiusProp = k3MFindProperty(this, k3M_PROP_BORDER_RADIUS, false); struct k3MProperty *borderRadiusProp = k3MFindProperty(this, k3M_PROP_BORDER_RADIUS, true);
float borderRadius = borderRadiusProp ? borderRadiusProp->si[0] : 0; float borderRadius = borderRadiusProp ? borderRadiusProp->si[0] : 0;
struct k3MProperty *bgColorProp = k3MFindProperty(this, k3M_PROP_BG_COLOR, false); struct k3MProperty *bgColorProp = k3MFindProperty(this, k3M_PROP_BG_COLOR, true);
vec4 bgColor = {0, 0, 0, 0}; vec4 bgColor = {0, 0, 0, 0};
if(bgColorProp) { if(bgColorProp) {
bgColor[0] = bgColorProp->si[0] / 255.0; bgColor[0] = bgColorProp->si[0] / 255.0;
@@ -83,31 +226,87 @@ static void obj_draw(struct k3MObj *this) {
bgColor[3] = bgColorProp->si[3] / 255.0; bgColor[3] = bgColorProp->si[3] / 255.0;
} }
k3BatchAdd(NULL, (struct k3RectF) {0, 0, 1, 1}, (struct k3RectF) { if(bgColor[3] != 0) {
this->x, this->y, immdraw_fill_rect(this->x, this->y, this->w, this->h, bgColor[0], bgColor[1], bgColor[2], bgColor[3], borderRadius);
this->w, this->h }
}, 0, bgColor, borderRadius);
} }
static bool label_draw(struct k3MEvent *ev, uint8_t *ud) { static bool label_draw(struct k3MEvent *ev, uint8_t *ud) {
struct k3MLabel *this = (void*) ev->target; struct k3MLabel *this = (void*) ev->target;
obj_draw((void*) this); obj_draw_direct((void*) this);
if(this->txt) { if(this->txt) {
k3FontDraw(this->font, this->x, this->y, this->sz, this->txt, (vec4) {1, 1, 1, 1}); float sz = this->sz;
struct k3MProperty *prop = k3MFindProperty(this, k3M_PROP_FONT_SIZE, false);
if(prop) sz = prop->f[0];
int ha = k3M_ALIGN_LEFT;
prop = k3MFindProperty(this, k3M_PROP_HORIZONTAL_ALIGNMENT, false);
if(prop) ha = prop->si[0];
int va = k3M_ALIGN_CENTER;
prop = k3MFindProperty(this, k3M_PROP_VERTICAL_ALIGNMENT, false);
if(prop) va = prop->si[0];
int16_t x = this->x;
int16_t y = this->y;
if(va != k3M_ALIGN_TOP) {
struct k3RectF txtsz;
k3FontSz(this->font, sz, this->txt, this->w, &txtsz);
if(va == k3M_ALIGN_CENTER) {
y += (this->h - txtsz.h) / 2;
} else if(va == k3M_ALIGN_BOTTOM) {
y += this->h - txtsz.h;
}
}
immdraw_font_draw(this->font, x, y, this->w, sz, strlen(this->txt), this->txt, ha,
1, 1, 1, 1);
} }
return false; return false;
} }
static bool label_measure(struct k3MEvent *ev, uint8_t *ud) {
struct k3MLabel *this = (void*) ev->target;
float sz = this->sz;
struct k3MProperty *prop = k3MFindProperty(this, k3M_PROP_FONT_SIZE, false);
if(prop) sz = prop->f[0];
struct k3RectF txtsz;
k3FontSz(this->font, sz, this->txt, -1, &txtsz);
this->w = ceilf(txtsz.w);
this->h = ceilf(txtsz.h);
return false;
}
static bool label_set_height_from_width(struct k3MEvent *ev, uint8_t *ud) {
struct k3MLabel *this = (void*) ev->target;
float sz = this->sz;
struct k3MProperty *prop = k3MFindProperty(this, k3M_PROP_FONT_SIZE, false);
if(prop) sz = prop->f[0];
int16_t pxsz[2];
immdraw_font_size(this->font, sz, this->txt, this->w, pxsz);
this->h = pxsz[1];
return true;
}
struct k3MLabel *k3MLabel(struct k3Font *font, float sz, const char *txt) { struct k3MLabel *k3MLabel(struct k3Font *font, float sz, const char *txt) {
struct k3MLabel *ret = calloc(1, sizeof(*ret)); struct k3MLabel *ret = calloc(1, sizeof(*ret));
ret->font = font; ret->font = font;
ret->sz = sz; ret->sz = sz;
ret->txt = txt; ret->txt = strdup(txt);
k3MRegisterEventHandler(ret, k3M_EVENT_DRAW, label_draw, NULL, 0); k3MRegisterEventHandler(ret, k3M_EVENT_DRAW, label_draw, NULL, 0);
k3MRegisterEventHandler(ret, k3M_EVENT_MEASURE, label_measure, NULL, 0);
k3MRegisterEventHandler(ret, k3M_EVENT_SET_HEIGHT_FROM_WIDTH, label_set_height_from_width, NULL, 0);
return ret; return ret;
} }
@@ -115,7 +314,7 @@ struct k3MLabel *k3MLabel(struct k3Font *font, float sz, const char *txt) {
int k3MRemoveChild(struct k3MObj *parent, struct k3MObj *child) { int k3MRemoveChild(struct k3MObj *parent, struct k3MObj *child) {
for(size_t i = 0; i < parent->childCount; i++) { for(size_t i = 0; i < parent->childCount; i++) {
if(parent->children[i] == child) { if(parent->children[i] == child) {
memcpy(&parent->children[i], &parent->children[i + 1], sizeof(*parent->children) * (parent->childCount - i - 1)); memmove(&parent->children[i], &parent->children[i + 1], sizeof(*parent->children) * (parent->childCount - i - 1));
parent->childCount--; parent->childCount--;
@@ -166,34 +365,65 @@ static bool screen_ev(struct k3MEvent *ev, uint8_t *ud) {
struct k3MScreen *this = (void*) ev->target; struct k3MScreen *this = (void*) ev->target;
if(ev->code == k3M_EVENT_MOUSE_PRESS || ev->code == k3M_EVENT_MOUSE_RELEASE || ev->code == k3M_EVENT_MOUSE_MOTION) { if(ev->code == k3M_EVENT_MOUSE_PRESS || ev->code == k3M_EVENT_MOUSE_RELEASE || ev->code == k3M_EVENT_MOUSE_MOTION) {
struct k3MObj *innermost = (void*) this; struct k3MObj *innermost;
while(innermost->childCount != 0) { if(this->mouseCapture) {
innermost = this->mouseCapture;
bool foundInner = false; } else {
innermost = (struct k3MObj*) this;
for(intmax_t i = innermost->childCount - 1; i >= 0; i--) { while(innermost->childCount != 0) {
struct k3MObj *child = innermost->children[i]; bool foundInner = false;
if(!child->invisible && ev->mouse.x >= child->x && ev->mouse.x < child->x + child->w && ev->mouse.y >= child->y && ev->mouse.y < child->y + child->h) { for(intmax_t i = innermost->childCount - 1; i >= 0; i--) {
innermost = child; struct k3MObj *child = innermost->children[i];
foundInner = true;
break; if(!child->invisible && ev->mouse.x >= child->x && ev->mouse.x < child->x + child->w && ev->mouse.y >= child->y && ev->mouse.y < child->y + child->h) {
innermost = child;
foundInner = true;
break;
}
} }
if(!foundInner) {
break;
}
} }
if(!foundInner) {
break;
}
} }
if(innermost != this->lastHover) { if(innermost != this->lastHover) {
if(this->lastHover) {
k3MEventSend(&(struct k3MEvent) {
.code = k3M_EVENT_MOUSE_LEAVE,
.kind = k3M_EVENTKIND_DIRECT,
.original = ev->original,
.target = this->lastHover,
.mouse = ev->mouse,
});
}
if(innermost != (void*) this) {
k3MEventSend(&(struct k3MEvent) {
.code = k3M_EVENT_MOUSE_ENTER,
.kind = k3M_EVENTKIND_DIRECT,
.original = ev->original,
.target = innermost,
.mouse = ev->mouse,
});
}
this->lastHover->hovered = false; this->lastHover->hovered = false;
innermost->hovered = true; innermost->hovered = true;
this->lastHover = innermost; this->lastHover = innermost;
@@ -205,16 +435,22 @@ static bool screen_ev(struct k3MEvent *ev, uint8_t *ud) {
this->keyboardFocus = innermost; this->keyboardFocus = innermost;
} }
ev->kind = k3M_EVENTKIND_BUBBLE; k3MEventSend(&(struct k3MEvent) {
ev->target = innermost; .code = ev->code,
k3MEventSend(ev); .kind = k3M_EVENTKIND_BUBBLE,
.original = ev->original,
.target = innermost,
.mouse = ev->mouse,
});
if(ev->code == k3M_EVENT_MOUSE_RELEASE) { if(ev->code == k3M_EVENT_MOUSE_RELEASE) {
ev->code = k3M_EVENT_MOUSE_CLICK; k3MEventSend(&(struct k3MEvent) {
.code = k3M_EVENT_MOUSE_CLICK,
ev->kind = k3M_EVENTKIND_DIRECT; .kind = k3M_EVENTKIND_DIRECT,
ev->target = innermost; .original = ev->original,
k3MEventSend(ev); .target = innermost,
.mouse = ev->mouse,
});
} }
} }
@@ -223,20 +459,25 @@ static bool screen_ev(struct k3MEvent *ev, uint8_t *ud) {
} else if(ev->code == k3M_EVENT_KEY_PRESS || ev->code == k3M_EVENT_KEY_RELEASE || ev->code == k3M_EVENT_INPUT) { } else if(ev->code == k3M_EVENT_KEY_PRESS || ev->code == k3M_EVENT_KEY_RELEASE || ev->code == k3M_EVENT_INPUT) {
if(this->keyboardFocus) { if(this->keyboardFocus) {
ev->kind = k3M_EVENTKIND_BUBBLE; k3MEventSend(&(struct k3MEvent) {
ev->target = (void*) this->keyboardFocus; .code = ev->code,
k3MEventSend(ev); .kind = k3M_EVENTKIND_BUBBLE,
.original = ev->original,
.target = this->keyboardFocus,
.key = ev->key,
});
} }
return true; return true;
} }
ev->kind = k3M_EVENTKIND_CAPTURE; for(intmax_t i = 0; i < this->childCount; i++) {
for(intmax_t i = this->childCount - 1; i >= 0; i--) {
if(!this->children[i]->invisible) { if(!this->children[i]->invisible) {
ev->target = this->children[i]; struct k3MEvent newev = *ev;
if(k3MEventSend(ev)) { newev.kind = k3M_EVENTKIND_CAPTURE;
newev.target = this->children[i];
if(k3MEventSend(&newev)) {
return true; return true;
} }
} }
@@ -244,38 +485,126 @@ static bool screen_ev(struct k3MEvent *ev, uint8_t *ud) {
return false; return false;
} }
static bool screen_arrange(struct k3MEvent *ev, uint8_t *ud) {
struct k3MObj *this = ev->target;
for(size_t c = 0; c < this->childCount; c++) {
struct k3MObj *child = this->children[c];
struct k3MProperty *left = k3MFindProperty(child, k3M_PROP_LEFT, true);
if(left) {
if(left->units[0] == k3M_UNIT_ABSOLUTE) {
child->x = this->x + left->f[0];
} else if(left->units[0] == k3M_UNIT_PROPORTION) {
child->x = this->x + this->w * left->f[0];
}
}
struct k3MProperty *top = k3MFindProperty(child, k3M_PROP_TOP, true);
if(top) {
if(top->units[0] == k3M_UNIT_ABSOLUTE) {
child->y = this->y + top->f[0];
} else if(top->units[0] == k3M_UNIT_PROPORTION) {
child->y = this->y + this->h * top->f[0];
}
}
struct k3MProperty *width = k3MFindProperty(child, k3M_PROP_WIDTH, true);
if(width) {
if(width->units[0] == k3M_UNIT_ABSOLUTE) {
child->w = width->f[0];
} else if(width->units[0] == k3M_UNIT_PROPORTION) {
child->w = this->w * width->f[0];
}
}
struct k3MProperty *height = k3MFindProperty(child, k3M_PROP_HEIGHT, true);
if(height) {
if(height->units[0] == k3M_UNIT_ABSOLUTE) {
child->h = height->f[0];
} else if(height->units[0] == k3M_UNIT_PROPORTION) {
child->h = this->h * height->f[0];
}
}
}
return false;
}
struct k3MScreen *k3MScreen() { struct k3MScreen *k3MScreen() {
struct k3MScreen *ret = calloc(1, sizeof(*ret)); struct k3MScreen *ret = calloc(1, sizeof(*ret));
ret->lastHover = (void*) ret; ret->lastHover = (void*) ret;
k3MRegisterEventHandler(ret, k3M_EVENT_ARRANGE, screen_arrange, NULL, 0);
k3MRegisterEventHandler(ret, k3M_EVENT_ALL, screen_ev, NULL, 0); k3MRegisterEventHandler(ret, k3M_EVENT_ALL, screen_ev, NULL, 0);
return ret; return ret;
} }
bool k3MCaptureMouse(struct k3MScreen *screen, struct k3MObj *capt) {
if(screen->mouseCapture) {
return false;
}
screen->mouseCapture = capt;
return true;
}
void k3MReleaseMouse(struct k3MScreen *screen) {
screen->mouseCapture = NULL;
}
static bool textbutton_draw(struct k3MEvent *ev, uint8_t *ud) { static bool textbutton_draw(struct k3MEvent *ev, uint8_t *ud) {
struct k3MTextButton *this = (void*) ev->target; struct k3MTextButton *this = (void*) ev->target;
obj_draw((void*) this); obj_draw_direct((void*) this);
if(this->txt) { if(this->txt) {
struct k3RectF txtsz; int ha = k3M_ALIGN_CENTER;
k3FontSz(this->font, this->sz, this->txt, &txtsz); struct k3MProperty *prop = k3MFindProperty(this, k3M_PROP_HORIZONTAL_ALIGNMENT, false);
if(prop) ha = prop->si[0];
k3FontDraw(this->font, this->x + this->w / 2 - txtsz.w / 2, this->y, this->sz, this->txt, (vec4) { int va = k3M_ALIGN_CENTER;
1 - 0.8 * this->disabled, 1 - 0.8 * this->disabled, 1 - 0.8 * this->disabled, 1}); prop = k3MFindProperty(this, k3M_PROP_VERTICAL_ALIGNMENT, false);
if(prop) va = prop->si[0];
int16_t x = this->x;
int16_t y = this->y;
if(va != k3M_ALIGN_TOP) {
struct k3RectF txtsz;
k3FontSz(this->font, this->sz, this->txt, this->w, &txtsz);
if(va == k3M_ALIGN_CENTER) {
y += (this->h - txtsz.h) / 2;
} else if(va == k3M_ALIGN_BOTTOM) {
y += this->h - txtsz.h;
}
}
immdraw_font_draw(this->font, x, y, this->w, this->sz, strlen(this->txt), this->txt, ha,
1 - 0.8 * this->disabled, 1 - 0.8 * this->disabled, 1 - 0.8 * this->disabled, 1);
} }
return false; return false;
} }
static bool textbutton_measure(struct k3MEvent *ev, uint8_t *ud) {
struct k3MTextButton *this = (void*) ev->target;
struct k3RectF sz = {};
k3FontSz(this->font, this->sz, this->txt, -1, &sz);
this->w = ceilf(sz.w);
this->h = ceilf(sz.h);
return false;
}
struct k3MTextButton *k3MTextButton(struct k3Font *font, float sz, const char *txt) { struct k3MTextButton *k3MTextButton(struct k3Font *font, float sz, const char *txt) {
struct k3MTextButton *ret = calloc(1, sizeof(*ret)); struct k3MTextButton *ret = calloc(1, sizeof(*ret));
ret->font = font; ret->font = font;
ret->sz = sz; ret->sz = sz;
ret->txt = txt; ret->txt = strdup(txt);
k3MRegisterEventHandler(ret, k3M_EVENT_MEASURE, textbutton_measure, NULL, 0);
k3MRegisterEventHandler(ret, k3M_EVENT_DRAW, textbutton_draw, NULL, 0); k3MRegisterEventHandler(ret, k3M_EVENT_DRAW, textbutton_draw, NULL, 0);
return ret; return ret;
@@ -284,16 +613,19 @@ struct k3MTextButton *k3MTextButton(struct k3Font *font, float sz, const char *t
static bool textinput_draw(struct k3MEvent *ev, uint8_t *ud) { static bool textinput_draw(struct k3MEvent *ev, uint8_t *ud) {
struct k3MTextInput *this = (void*) ev->target; struct k3MTextInput *this = (void*) ev->target;
obj_draw((void*) this); obj_draw_direct((void*) this);
if(this->txt) { if(this->txt) {
int isPlaceholder = strlen(this->txt) == 0; bool isPlaceholder = strlen(this->txt) == 0;
const char *txt = isPlaceholder ? this->placeholder : this->txt; const char *txt = isPlaceholder ? this->placeholder : this->txt;
struct k3RectF txtsz; /*struct k3RectF txtsz;
k3FontSz(this->font, this->sz, txt, &txtsz); k3FontSz(this->font, this->sz, txt, &txtsz);
k3FontDraw(this->font, this->x, this->y, this->sz, txt, (vec4) {1 - 0.5 * isPlaceholder, 1 - 0.5 * isPlaceholder, 1 - 0.5 * isPlaceholder, 1}); k3FontDraw(this->font, this->x, this->y, this->sz, txt, (vec4) {1 - 0.5 * isPlaceholder, 1 - 0.5 * isPlaceholder, 1 - 0.5 * isPlaceholder, 1});*/
immdraw_font_draw(this->font, this->x, this->y, this->w, this->sz, strlen(txt), txt, k3M_ALIGN_LEFT,
1 - 0.5 * isPlaceholder, 1 - 0.5 * isPlaceholder, 1 - 0.5 * isPlaceholder, 1);
} }
return false; return false;
@@ -302,7 +634,7 @@ static bool textinput_key(struct k3MEvent *ev, uint8_t *ud) {
struct k3MTextInput *this = (void*) ev->target; struct k3MTextInput *this = (void*) ev->target;
if(ev->key.num == GLFW_KEY_BACKSPACE && ev->code != k3M_EVENT_KEY_RELEASE) { if(ev->key.num == GLFW_KEY_BACKSPACE && ev->code != k3M_EVENT_KEY_RELEASE) {
char *last = k3UTF8LastCodepointZ(this->txt); char *last = (char*) k3UTF8LastCodepointZ(this->txt);
if(last) { if(last) {
*last = 0; *last = 0;
} }
@@ -336,7 +668,7 @@ struct k3MTextInput *k3MTextInput(struct k3Font *font, float sz, const char *pla
ret->font = font; ret->font = font;
ret->sz = sz; ret->sz = sz;
ret->placeholder = placeholder ? placeholder : ""; ret->placeholder = strdup(placeholder ? placeholder : "");
ret->txt = strdup(txt ? txt : ""); ret->txt = strdup(txt ? txt : "");
k3MRegisterEventHandler(ret, k3M_EVENT_DRAW, textinput_draw, NULL, 0); k3MRegisterEventHandler(ret, k3M_EVENT_DRAW, textinput_draw, NULL, 0);
@@ -347,7 +679,174 @@ struct k3MTextInput *k3MTextInput(struct k3Font *font, float sz, const char *pla
return ret; return ret;
} }
static bool obj_draw(struct k3MEvent *ev, uint8_t *ud) {
obj_draw_direct(ev->target);
return false;
}
struct k3MObj *k3MObj() { struct k3MObj *k3MObj() {
struct k3MObj *ret = calloc(1, sizeof(*ret)); struct k3MObj *ret = calloc(1, sizeof(*ret));
k3MRegisterEventHandler(ret, k3M_EVENT_DRAW, obj_draw, NULL, 0);
return ret; return ret;
} }
static bool image_draw(struct k3MEvent *ev, uint8_t *ud) {
struct k3MImage *this = (void*) ev->target;
struct k3MProperty *propFGColor = k3MFindProperty(ev->target, k3M_PROP_FG_COLOR, false);
vec4 color = {1, 1, 1, 1};
if(propFGColor) {
color[0] = propFGColor->si[0] / 255.0;
color[1] = propFGColor->si[1] / 255.0;
color[2] = propFGColor->si[2] / 255.0;
color[3] = propFGColor->si[3] / 255.0;
}
immdraw_image_draw(&this->data, this->x, this->y, this->w, this->h, color[0], color[1], color[2], color[3]);
return false;
}
struct k3MImage *k3MImage(k3MImageData data) {
struct k3MImage *ret = calloc(1, sizeof(*ret));
ret->data = data;
k3MRegisterEventHandler(ret, k3M_EVENT_DRAW, image_draw, NULL, 0);
return ret;
}
#define SCROLLBAR_WIDTH_DEFAULT 16
static bool scrollbox_arrange(struct k3MEvent *ev, uint8_t *ud) {
struct k3MScrollbox *this = (void*) ev->target;
struct k3MObj *contentBox = this->children[0];
contentBox->x = this->x;
contentBox->y = this->y;
contentBox->w = this->w - 16;
contentBox->h = 16384;
return false;
}
static bool scrollbox_post_arrange(struct k3MEvent *ev, uint8_t *ud) {
struct k3MScrollbox *this = (void*) ev->target;
struct k3MObj *contentBox = this->children[0];
size_t maxY2 = 0;
for(size_t c = 0; c < contentBox->childCount; c++) {
size_t y2 = contentBox->children[c]->y + contentBox->children[c]->h;
if(maxY2 < y2) {
maxY2 = y2;
}
}
contentBox->h = maxY2 - contentBox->y;
if(this->h > contentBox->h) {
this->scrollbarLength = this->h;
} else {
this->scrollbarLength = this->h * this->h / contentBox->h;
}
return false;
}
static bool scrollbox_draw(struct k3MEvent *ev, uint8_t *ud) {
struct k3MScrollbox *this = (void*) ev->target;
int scrollbarWidth = SCROLLBAR_WIDTH_DEFAULT;
struct k3MProperty *prop = k3MFindProperty(ev->target, k3M_PROP_SCROLLBAR_SIZE, false);
if(prop) {
scrollbarWidth = prop->si[0];
}
immdraw_fill_rect(this->x + this->w - scrollbarWidth, this->y + this->scrollbarOffset, scrollbarWidth, this->scrollbarLength, 1, 1, 1, 1, 0);
return false;
}
static bool scrollbox_mouseshit(struct k3MEvent *ev, uint8_t *ud) {
struct k3MScrollbox *this = (void*) ev->target;
if(ev->code == k3M_EVENT_MOUSE_PRESS) {
if(k3MCaptureMouse((struct k3MScreen*) ev->original, this)) {
struct k3MProperty *prop = k3MFindProperty(ev->target, k3M_PROP_SCROLL_ANYWHERE, false);
bool scrollAnywhere = prop ? prop->si[0] : false;
int scrollbarWidth = SCROLLBAR_WIDTH_DEFAULT;
prop = k3MFindProperty(ev->target, k3M_PROP_SCROLLBAR_SIZE, false);
if(prop) {
scrollbarWidth = prop->si[0];
}
if(ev->mouse.x >= this->x + this->w - scrollbarWidth) {
this->mouseHeld = 1;
} else if(scrollAnywhere) {
this->mouseHeld = 2;
} else {
this->mouseHeld = 0;
}
this->mouseX = ev->mouse.x;
this->mouseY = ev->mouse.y;
return !!this->mouseHeld;
}
} else if(ev->code == k3M_EVENT_MOUSE_RELEASE) {
if(this->mouseHeld) {
this->mouseHeld = false;
k3MReleaseMouse((struct k3MScreen*) ev->original);
return true;
}
} else if(ev->code == k3M_EVENT_MOUSE_MOTION) {
if(this->mouseHeld) {
int16_t diffX = ev->mouse.x - this->mouseX;
int16_t diffY = ev->mouse.y - this->mouseY;
this->mouseX = ev->mouse.x;
this->mouseY = ev->mouse.y;
struct k3MObj *contentBox = this->children[0];
if(this->mouseHeld == 1) {
this->scrollbarOffset += diffY;
contentBox->y = (float) this->y + this->scrollbarOffset * (-contentBox->h + this->h) / (this->h - this->scrollbarLength);
} else if(this->mouseHeld == 2) {
contentBox->y += diffY;
}
if(contentBox->y > this->y) {
contentBox->y = this->y;
} else if(contentBox->y < this->y - contentBox->h + this->h) {
contentBox->y = this->y - contentBox->h + this->h;
}
this->scrollbarOffset = (float) (contentBox->y - this->y) * (this->h - this->scrollbarLength) / (this->h - contentBox->h);
k3MArrange(contentBox);
return true;
}
}
return false;
}
struct k3MScrollbox *k3MScrollbox() {
struct k3MScrollbox *this = calloc(1, sizeof(*this));
struct k3MObj *contentBox = k3MObj();
k3MAddChild(this, contentBox);
k3MRegisterEventHandler(this, k3M_EVENT_ARRANGE, scrollbox_arrange, NULL, 0);
k3MRegisterEventHandler(this, k3M_EVENT_POST_ARRANGE, scrollbox_post_arrange, NULL, 0);
k3MRegisterEventHandler(this, k3M_EVENT_DRAW, scrollbox_draw, NULL, 0);
k3MRegisterEventHandler(this, k3M_EVENT_MOUSE_PRESS, scrollbox_mouseshit, NULL, 0);
k3MRegisterEventHandler(this, k3M_EVENT_MOUSE_MOTION, scrollbox_mouseshit, NULL, 0);
k3MRegisterEventHandler(this, k3M_EVENT_MOUSE_RELEASE, scrollbox_mouseshit, NULL, 0);
this->crop = true;
return this;
}
struct k3MObj *k3MScrollboxGetContent(struct k3MScrollbox *this) {
return this->children[0];
}

View File

@@ -3,8 +3,9 @@
#include<stdint.h> #include<stdint.h>
#include<stdlib.h> #include<stdlib.h>
#include<stdbool.h> #include<stdbool.h>
#include"k3font.h" #include<stddef.h>
struct k3Font;
struct k3MObj; struct k3MObj;
#define k3M_EVENT_MOUSE_ENTER 0 #define k3M_EVENT_MOUSE_ENTER 0
@@ -18,7 +19,11 @@ struct k3MObj;
#define k3M_EVENT_INPUT 8 #define k3M_EVENT_INPUT 8
#define k3M_EVENT_DRAW 9 #define k3M_EVENT_DRAW 9
#define k3M_EVENT_COMPLETE 10 #define k3M_EVENT_COMPLETE 10
#define k3M_EVENT_ALL 11 #define k3M_EVENT_MEASURE 11
#define k3M_EVENT_ARRANGE 12
#define k3M_EVENT_POST_ARRANGE 13
#define k3M_EVENT_SET_HEIGHT_FROM_WIDTH 14
#define k3M_EVENT_ALL 15
#define k3M_MOUSE_BUTTON_0 0 #define k3M_MOUSE_BUTTON_0 0
#define k3M_MOUSE_BUTTON_1 1 #define k3M_MOUSE_BUTTON_1 1
@@ -28,7 +33,29 @@ struct k3MObj;
#define k3M_EVENTKIND_CAPTURE 1 #define k3M_EVENTKIND_CAPTURE 1
#define k3M_EVENTKIND_BUBBLE 2 #define k3M_EVENTKIND_BUBBLE 2
#define k3M_USERDATA_SIZE 16 #define k3M_USERDATA_SIZE 32
#define k3M_ALIGN_LEFT 0
#define k3M_ALIGN_TOP 3
#define k3M_ALIGN_CENTER 1
#define k3M_ALIGN_RIGHT 2
#define k3M_ALIGN_BOTTOM 4
#define k3M_IDX_TOP 0
#define k3M_IDX_RIGHT 1
#define k3M_IDX_BOTTOM 2
#define k3M_IDX_LEFT 3
#ifdef k3M_FIXED_POINT
typedef uint8_t k3MCC;
#else
typedef float k3MCC;
#endif
#define k3M_UNIT_ABSOLUTE 0
#define k3M_UNIT_PROPORTION 1
#include"k3menu_internal.h"
struct k3MEvent { struct k3MEvent {
uint16_t code; uint16_t code;
@@ -63,14 +90,28 @@ typedef struct k3MEventHandler {
enum k3MPropertyType { enum k3MPropertyType {
k3M_PROP_BG_COLOR, k3M_PROP_BG_COLOR,
k3M_PROP_FG_COLOR,
k3M_PROP_BORDER_RADIUS, k3M_PROP_BORDER_RADIUS,
k3M_PROP_MARGIN,
k3M_PROP_PADDING,
k3M_PROP_HORIZONTAL_ALIGNMENT,
k3M_PROP_VERTICAL_ALIGNMENT,
k3M_PROP_LEFT,
k3M_PROP_TOP,
k3M_PROP_WIDTH,
k3M_PROP_HEIGHT,
k3M_PROP_FONT_SIZE,
k3M_PROP_SCROLL_ANYWHERE,
k3M_PROP_SCROLLBAR_SIZE,
}; };
struct k3MProperty { struct k3MProperty {
enum k3MPropertyType type; enum k3MPropertyType type;
uint8_t units[4];
union { union {
intmax_t si[4]; intmax_t si[4];
void *ptr; void *ptr[4];
uint8_t buf[32]; uint8_t buf[32];
float f[4];
}; };
}; };
@@ -85,7 +126,10 @@ struct k3MObj {
int16_t w; int16_t w;
int16_t h; int16_t h;
bool invisible, hovered, disabled; int16_t wDesired;
int16_t hDesired;
bool invisible, hovered, disabled, crop;
k3MEventHandler *handlers; k3MEventHandler *handlers;
size_t handlerCount; size_t handlerCount;
@@ -93,6 +137,16 @@ struct k3MObj {
struct k3MProperty *properties; struct k3MProperty *properties;
size_t propertyCount; size_t propertyCount;
}; };
struct k3MObj *k3MObj();
/* Akin to the measure pass of WPF. Recursive, called by parent. Sets wDesired and hDesired. */
void k3MMeasure(struct k3MObj *this);
/* Akin to the arrange pass of WPF. Recursive, called by parent after w and h is set. Adjusts all children using it's own definitive size. */
void k3MArrange(struct k3MObj *this);
/* Set linear layout management on this object. Must not be called on descendants of k3MObj. */
void k3MSetLayoutLinear(struct k3MObj *this, bool vertical);
#define k3MenuSetBounds(a, x, y, w, h) k3MenuSetBounds_((struct k3MObj*) (a), (x), (y), (w), (h)) #define k3MenuSetBounds(a, x, y, w, h) k3MenuSetBounds_((struct k3MObj*) (a), (x), (y), (w), (h))
void k3MenuSetBounds_(struct k3MObj *this, int16_t x, int16_t y, int16_t w, int16_t h); void k3MenuSetBounds_(struct k3MObj *this, int16_t x, int16_t y, int16_t w, int16_t h);
@@ -122,8 +176,12 @@ struct k3MScreen {
struct k3MObj *keyboardFocus; struct k3MObj *keyboardFocus;
struct k3MObj *lastHover; struct k3MObj *lastHover;
struct k3MObj *mouseCapture;
}; };
struct k3MScreen *k3MScreen(); struct k3MScreen *k3MScreen();
bool k3MCaptureMouse(struct k3MScreen*, struct k3MObj*);
void k3MReleaseMouse(struct k3MScreen*);
struct k3MTextButton { struct k3MTextButton {
struct k3MObj; struct k3MObj;
@@ -145,3 +203,23 @@ struct k3MTextInput {
char *txt; char *txt;
}; };
struct k3MTextInput *k3MTextInput(struct k3Font *font, float sz, const char *placeholder, const char *txt); struct k3MTextInput *k3MTextInput(struct k3Font *font, float sz, const char *placeholder, const char *txt);
struct k3MImage {
struct k3MObj;
k3MImageData data;
};
struct k3MImage *k3MImage(k3MImageData);
struct k3MScrollbox {
struct k3MObj;
int mouseHeld;
int16_t mouseX;
int16_t mouseY;
float scrollbarOffset;
int16_t scrollbarLength;
};
struct k3MScrollbox *k3MScrollbox();
struct k3MObj *k3MScrollboxGetContent(struct k3MScrollbox*);

4
src/k3menu_internal.h Normal file
View File

@@ -0,0 +1,4 @@
#pragma once
struct k3Tex;
typedef struct k3Tex *k3MImageData;

View File

@@ -67,10 +67,10 @@ static void random_cone_vector(float coneAngle, vec3 output) {
} }
static void regenerate_model(struct k3CPUQuadParticles *this, vec3 cameraRight, vec3 cameraUp, vec3 cameraFront) { static void regenerate_model(struct k3CPUQuadParticles *this, vec3 cameraRight, vec3 cameraUp, vec3 cameraFront) {
vec3 *vpos = calloc(sizeof(*vpos), 4 * this->capacity); vec3 *vpos = _mm_malloc(sizeof(*vpos) * 4 * this->capacity, 16);
uint8_t *vcols = calloc(4, 4 * this->capacity); uint8_t *vcols = _mm_malloc(4 * 4 * this->capacity, 16);
vec2 *vuvs = calloc(sizeof(*vuvs), 4 * this->capacity); vec2 *vuvs = _mm_malloc(sizeof(*vuvs) * 4 * this->capacity, 16);
uint8_t *vnrms = calloc(3, 4 * this->capacity); uint8_t *vnrms = _mm_malloc(3 * 4 * this->capacity, 16);
vec3 halfRight, halfUp; vec3 halfRight, halfUp;
glm_vec3_scale(cameraRight, 0.5, halfRight); glm_vec3_scale(cameraRight, 0.5, halfRight);
@@ -128,10 +128,10 @@ static void regenerate_model(struct k3CPUQuadParticles *this, vec3 cameraRight,
this->mdl->meshes[0].idxNumber = this->count * 6; this->mdl->meshes[0].idxNumber = this->count * 6;
free(vpos); _mm_free(vpos);
free(vcols); _mm_free(vcols);
free(vuvs); _mm_free(vuvs);
free(vnrms); _mm_free(vnrms);
} }
static void copy_particle(struct k3CPUQuadParticles *this, size_t from, size_t to) { static void copy_particle(struct k3CPUQuadParticles *this, size_t from, size_t to) {

View File

@@ -5,6 +5,19 @@
#include"k3.h" #include"k3.h"
struct k3CPUQuadParticles { struct k3CPUQuadParticles {
vec4 colorStart;
vec4 colorEnd;
vec3 origin;
vec3 gravity;
vec3 emissionConeDirection;
float emissionRate;
float emissionConeAngle;
float particleLifetime;
bool emissionEnabled;
float emissionLifetime;
size_t capacity; size_t capacity;
size_t count; size_t count;
vec3 *positions; vec3 *positions;
@@ -13,19 +26,6 @@ struct k3CPUQuadParticles {
float *lifetimes; float *lifetimes;
struct k3Mdl *mdl; struct k3Mdl *mdl;
vec4 colorStart;
vec4 colorEnd;
vec3 origin;
vec3 gravity;
float emissionRate;
vec3 emissionConeDirection;
float emissionConeAngle;
float particleLifetime;
bool emissionEnabled;
float emissionLifetime;
}; };
void k3CPUQuadParticlesInit(struct k3CPUQuadParticles*, struct k3Mat*); void k3CPUQuadParticlesInit(struct k3CPUQuadParticles*, struct k3Mat*);