[RELEASE] u22-v03
This version is submitted to U22 breau.
This commit is contained in:
12
util/CMakeLists.txt
Normal file
12
util/CMakeLists.txt
Normal file
@@ -0,0 +1,12 @@
|
||||
add_subdirectory(chaos)
|
||||
add_subdirectory(coly2d)
|
||||
add_subdirectory(container)
|
||||
add_subdirectory(conv)
|
||||
add_subdirectory(dictres)
|
||||
add_subdirectory(gleasy)
|
||||
add_subdirectory(glyphas)
|
||||
add_subdirectory(jukebox)
|
||||
add_subdirectory(math)
|
||||
add_subdirectory(memory)
|
||||
add_subdirectory(mpkutil)
|
||||
add_subdirectory(parsarg)
|
7
util/chaos/CMakeLists.txt
Normal file
7
util/chaos/CMakeLists.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
add_library(chaos
|
||||
abchash.c
|
||||
xorshift.c
|
||||
)
|
||||
target_link_libraries(chaos
|
||||
math
|
||||
)
|
32
util/chaos/abchash.c
Normal file
32
util/chaos/abchash.c
Normal file
@@ -0,0 +1,32 @@
|
||||
#include "./abchash.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define CHAOS_ABCHASH_GET_CODE(c) ( \
|
||||
('0' <= c && c <= '9')? c-'0': \
|
||||
('a' <= c && c <= 'z')? c-'a'+10: \
|
||||
(c == '_') ? 36: UINT8_MAX)
|
||||
|
||||
#define CHAOS_ABCHASH_MAX_CODE 36
|
||||
|
||||
uint64_t chaos_abchash(const char* str, size_t len) {
|
||||
assert(chaos_abchash_validate(str, len));
|
||||
|
||||
uint64_t ans = 0;
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
ans = ans*(CHAOS_ABCHASH_MAX_CODE+1) + CHAOS_ABCHASH_GET_CODE(str[i]);
|
||||
}
|
||||
return ans;
|
||||
}
|
||||
|
||||
bool chaos_abchash_validate(const char* str, size_t len) {
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
if (CHAOS_ABCHASH_GET_CODE(str[i]) > CHAOS_ABCHASH_MAX_CODE) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return len > 0;
|
||||
}
|
19
util/chaos/abchash.h
Normal file
19
util/chaos/abchash.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* DON'T EXPECT THIS ALGORITHM TO BE ONE-WAY */
|
||||
|
||||
uint64_t
|
||||
chaos_abchash(
|
||||
const char* str,
|
||||
size_t len
|
||||
);
|
||||
|
||||
bool
|
||||
chaos_abchash_validate(
|
||||
const char* str,
|
||||
size_t len
|
||||
);
|
19
util/chaos/xorshift.c
Normal file
19
util/chaos/xorshift.c
Normal file
@@ -0,0 +1,19 @@
|
||||
#include "./xorshift.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
uint64_t chaos_xorshift(uint64_t seed) {
|
||||
seed = seed ^ (seed << 13);
|
||||
seed = seed ^ (seed >> 7);
|
||||
return seed ^ (seed << 17);
|
||||
}
|
||||
|
||||
float chaos_xorshift_fract(uint64_t seed, uint64_t* next_seed) {
|
||||
static const uint64_t period = 10000;
|
||||
|
||||
seed = chaos_xorshift(seed);
|
||||
if (next_seed != NULL) *next_seed = seed;
|
||||
|
||||
return seed%period*1.f/period;
|
||||
}
|
14
util/chaos/xorshift.h
Normal file
14
util/chaos/xorshift.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
uint64_t
|
||||
chaos_xorshift(
|
||||
uint64_t seed
|
||||
);
|
||||
|
||||
float
|
||||
chaos_xorshift_fract(
|
||||
uint64_t seed,
|
||||
uint64_t* next_seed /* NULLABLE */
|
||||
);
|
7
util/coly2d/CMakeLists.txt
Normal file
7
util/coly2d/CMakeLists.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
add_library(coly2d
|
||||
hittest.c
|
||||
shape.c
|
||||
)
|
||||
target_link_libraries(coly2d
|
||||
math
|
||||
)
|
4
util/coly2d/README.md
Normal file
4
util/coly2d/README.md
Normal file
@@ -0,0 +1,4 @@
|
||||
coly
|
||||
====
|
||||
|
||||
simplest 2D collision calculation library for C
|
171
util/coly2d/hittest.c
Normal file
171
util/coly2d/hittest.c
Normal file
@@ -0,0 +1,171 @@
|
||||
#include "./hittest.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "util/math/algorithm.h"
|
||||
#include "util/math/vector.h"
|
||||
|
||||
bool coly2d_hittest_point_and_rect(
|
||||
const vec2_t* pos1, const vec2_t* pos2, const vec2_t* size2) {
|
||||
assert(vec2_valid(pos1));
|
||||
assert(vec2_valid(pos2));
|
||||
assert(vec2_valid(size2));
|
||||
|
||||
vec2_t rpos;
|
||||
vec2_sub(&rpos, pos2, pos1);
|
||||
return
|
||||
MATH_ABS(rpos.x) < size2->x &&
|
||||
MATH_ABS(rpos.y) < size2->y;
|
||||
}
|
||||
|
||||
bool coly2d_hittest_point_and_triangle(
|
||||
const vec2_t* pos1,
|
||||
const vec2_t* pos2_a,
|
||||
const vec2_t* pos2_b,
|
||||
const vec2_t* pos2_c) {
|
||||
assert(vec2_valid(pos1));
|
||||
assert(vec2_valid(pos2_a));
|
||||
assert(vec2_valid(pos2_b));
|
||||
assert(vec2_valid(pos2_c));
|
||||
|
||||
vec2_t v1, v2;
|
||||
|
||||
vec2_sub(&v1, pos1, pos2_a);
|
||||
vec2_sub(&v2, pos2_b, pos2_a);
|
||||
float a = vec2_cross(&v1, &v2);
|
||||
|
||||
vec2_sub(&v1, pos1, pos2_b);
|
||||
vec2_sub(&v2, pos2_c, pos2_b);
|
||||
if (a * vec2_cross(&v1, &v2) <= 0) return false;
|
||||
|
||||
vec2_sub(&v1, pos1, pos2_c);
|
||||
vec2_sub(&v2, pos2_a, pos2_c);
|
||||
return a*vec2_cross(&v1, &v2) > 0;
|
||||
}
|
||||
|
||||
bool coly2d_hittest_lineseg_and_lineseg(
|
||||
const vec2_t* pos1_st,
|
||||
const vec2_t* pos1_ed,
|
||||
const vec2_t* pos2_st,
|
||||
const vec2_t* pos2_ed) {
|
||||
assert(vec2_valid(pos1_st));
|
||||
assert(vec2_valid(pos1_ed));
|
||||
assert(vec2_valid(pos2_st));
|
||||
assert(vec2_valid(pos2_ed));
|
||||
|
||||
vec2_t x;
|
||||
vec2_sub(&x, pos1_ed, pos1_st);
|
||||
vec2_t a;
|
||||
vec2_sub(&a, pos2_st, pos1_st);
|
||||
vec2_t b;
|
||||
vec2_sub(&b, pos2_ed, pos1_st);
|
||||
|
||||
vec2_t y;
|
||||
vec2_sub(&y, pos2_ed, pos2_st);
|
||||
vec2_t c;
|
||||
vec2_sub(&c, pos1_st, pos2_st);
|
||||
vec2_t d;
|
||||
vec2_sub(&d, pos1_ed, pos2_st);
|
||||
|
||||
return
|
||||
vec2_cross(&x, &a) * vec2_cross(&x, &b) < 0 &&
|
||||
vec2_cross(&y, &c) * vec2_cross(&y, &d) < 0;
|
||||
}
|
||||
|
||||
bool coly2d_hittest_lineseg_and_rect(
|
||||
const vec2_t* pos1_st,
|
||||
const vec2_t* pos1_ed,
|
||||
const vec2_t* pos2,
|
||||
const vec2_t* size2) {
|
||||
assert(vec2_valid(pos1_st));
|
||||
assert(vec2_valid(pos1_ed));
|
||||
assert(vec2_valid(pos2));
|
||||
assert(vec2_valid(size2));
|
||||
|
||||
if (coly2d_hittest_point_and_rect(pos1_st, pos2, size2) ||
|
||||
coly2d_hittest_point_and_rect(pos1_ed, pos2, size2)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const float left = pos2->x - size2->x;
|
||||
const float right = pos2->x + size2->x;
|
||||
const float top = pos2->y + size2->y;
|
||||
const float bottom = pos2->y - size2->y;
|
||||
|
||||
const vec2_t v1 = vec2(left, top);
|
||||
const vec2_t v2 = vec2(left, bottom);
|
||||
const vec2_t v3 = vec2(right, bottom);
|
||||
const vec2_t v4 = vec2(right, top);
|
||||
|
||||
return
|
||||
coly2d_hittest_lineseg_and_lineseg(pos1_st, pos1_ed, &v1, &v2) ||
|
||||
coly2d_hittest_lineseg_and_lineseg(pos1_st, pos1_ed, &v2, &v3) ||
|
||||
coly2d_hittest_lineseg_and_lineseg(pos1_st, pos1_ed, &v3, &v4) ||
|
||||
coly2d_hittest_lineseg_and_lineseg(pos1_st, pos1_ed, &v4, &v1);
|
||||
}
|
||||
|
||||
bool coly2d_hittest_lineseg_and_ellipse(
|
||||
const vec2_t* pos1_st,
|
||||
const vec2_t* pos1_ed,
|
||||
const vec2_t* pos2,
|
||||
const vec2_t* size2) {
|
||||
assert(vec2_valid(pos1_st));
|
||||
assert(vec2_valid(pos1_ed));
|
||||
assert(vec2_valid(pos2));
|
||||
assert(vec2_valid(size2));
|
||||
|
||||
vec2_t p2 = *pos2;
|
||||
p2.x /= size2->x;
|
||||
p2.y /= size2->y;
|
||||
|
||||
vec2_t p1st = *pos1_st;
|
||||
p1st.x /= size2->x;
|
||||
p1st.y /= size2->y;
|
||||
|
||||
vec2_t c1;
|
||||
vec2_sub(&c1, &p2, &p1st);
|
||||
if (vec2_pow_length(&c1) < 1) return true;
|
||||
|
||||
vec2_t p1ed = *pos1_ed;
|
||||
p1ed.x /= size2->x;
|
||||
p1ed.y /= size2->y;
|
||||
|
||||
vec2_t c2;
|
||||
vec2_sub(&c2, &p2, &p1ed);
|
||||
if (vec2_pow_length(&c2) < 1) return true;
|
||||
|
||||
vec2_t ed;
|
||||
vec2_sub(&ed, &p1ed, &p1st);
|
||||
const float cross = vec2_cross(&ed, &c1);
|
||||
if (MATH_ABS(cross) >= vec2_length(&ed)) return false;
|
||||
|
||||
vec2_t st;
|
||||
vec2_sub(&st, &p1st, &p1ed);
|
||||
return vec2_dot(&c1, &ed)*vec2_dot(&c2, &st) >= 0;
|
||||
}
|
||||
|
||||
bool coly2d_hittest_lineseg_and_triangle(
|
||||
const vec2_t* pos1_st,
|
||||
const vec2_t* pos1_ed,
|
||||
const vec2_t* pos2_a,
|
||||
const vec2_t* pos2_b,
|
||||
const vec2_t* pos2_c) {
|
||||
assert(vec2_valid(pos1_st));
|
||||
assert(vec2_valid(pos1_ed));
|
||||
assert(vec2_valid(pos2_a));
|
||||
assert(vec2_valid(pos2_b));
|
||||
assert(vec2_valid(pos2_c));
|
||||
|
||||
/* TODO(catfoot): Tomas Moller's algorithm may make this function faster. */
|
||||
|
||||
if (coly2d_hittest_lineseg_and_lineseg(pos1_st, pos1_ed, pos2_a, pos2_b) ||
|
||||
coly2d_hittest_lineseg_and_lineseg(pos1_st, pos1_ed, pos2_b, pos2_c) ||
|
||||
coly2d_hittest_lineseg_and_lineseg(pos1_st, pos1_ed, pos2_c, pos2_a)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return
|
||||
coly2d_hittest_point_and_triangle(pos1_st, pos2_a, pos2_b, pos2_c) ||
|
||||
coly2d_hittest_point_and_triangle(pos1_ed, pos2_a, pos2_b, pos2_c);
|
||||
}
|
53
util/coly2d/hittest.h
Normal file
53
util/coly2d/hittest.h
Normal file
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "util/math/vector.h"
|
||||
|
||||
bool
|
||||
coly2d_hittest_point_and_rect(
|
||||
const vec2_t* pos1,
|
||||
const vec2_t* pos2,
|
||||
const vec2_t* size2
|
||||
);
|
||||
|
||||
bool
|
||||
coly2d_hittest_point_and_triangle(
|
||||
const vec2_t* pos1,
|
||||
const vec2_t* pos2_a,
|
||||
const vec2_t* pos2_b,
|
||||
const vec2_t* pos2_c
|
||||
);
|
||||
|
||||
bool
|
||||
coly2d_hittest_lineseg_and_lineseg(
|
||||
const vec2_t* pos1_st,
|
||||
const vec2_t* pos1_ed,
|
||||
const vec2_t* pos2_st,
|
||||
const vec2_t* pos2_ed
|
||||
);
|
||||
|
||||
bool
|
||||
coly2d_hittest_lineseg_and_rect(
|
||||
const vec2_t* pos1_st,
|
||||
const vec2_t* pos1_ed,
|
||||
const vec2_t* pos2,
|
||||
const vec2_t* size2
|
||||
);
|
||||
|
||||
bool
|
||||
coly2d_hittest_lineseg_and_ellipse(
|
||||
const vec2_t* pos1_st,
|
||||
const vec2_t* pos1_ed,
|
||||
const vec2_t* pos2,
|
||||
const vec2_t* size2
|
||||
);
|
||||
|
||||
bool
|
||||
coly2d_hittest_lineseg_and_triangle(
|
||||
const vec2_t* pos1_st,
|
||||
const vec2_t* pos1_ed,
|
||||
const vec2_t* pos2_a,
|
||||
const vec2_t* pos2_b,
|
||||
const vec2_t* pos2_c
|
||||
);
|
54
util/coly2d/shape.c
Normal file
54
util/coly2d/shape.c
Normal file
@@ -0,0 +1,54 @@
|
||||
#include "./shape.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include "util/math/algorithm.h"
|
||||
#include "util/math/vector.h"
|
||||
|
||||
#include "./hittest.h"
|
||||
|
||||
bool coly2d_shape_valid(const coly2d_shape_t* shape) {
|
||||
return
|
||||
shape != NULL &&
|
||||
vec2_valid(&shape->size) &&
|
||||
MATH_FLOAT_VALID(shape->angle);
|
||||
}
|
||||
|
||||
bool coly2d_shape_hittest_lineseg(
|
||||
const coly2d_shape_t* shape, const vec2_t* st, const vec2_t* ed) {
|
||||
assert(coly2d_shape_valid(shape));
|
||||
assert(vec2_valid(st));
|
||||
assert(vec2_valid(ed));
|
||||
|
||||
static const vec2_t origin = vec2(0, 0);
|
||||
|
||||
const float s = -sin(shape->angle);
|
||||
const float c = cos(shape->angle);
|
||||
|
||||
const vec2_t st_ = vec2(st->x*c-st->y*s, st->x*s+st->y*c);
|
||||
const vec2_t ed_ = vec2(ed->x*c-ed->y*s, ed->x*s+ed->y*c);
|
||||
|
||||
switch (shape->type) {
|
||||
case COLY2D_SHAPE_TYPE_LINE:
|
||||
return coly2d_hittest_lineseg_and_lineseg(
|
||||
&st_, &ed_, &origin, &shape->size);
|
||||
case COLY2D_SHAPE_TYPE_RECT:
|
||||
return coly2d_hittest_lineseg_and_rect(
|
||||
&st_, &ed_, &origin, &shape->size);
|
||||
case COLY2D_SHAPE_TYPE_TRIANGLE:
|
||||
return coly2d_hittest_lineseg_and_triangle(
|
||||
&st_, &ed_,
|
||||
&vec2( shape->size.x, 0),
|
||||
&vec2(-shape->size.x, shape->size.y),
|
||||
&vec2(-shape->size.x, -shape->size.y));
|
||||
case COLY2D_SHAPE_TYPE_ELLIPSE:
|
||||
return coly2d_hittest_lineseg_and_ellipse(
|
||||
&st_, &ed_, &origin, &shape->size);
|
||||
}
|
||||
|
||||
assert(false);
|
||||
return false;
|
||||
}
|
51
util/coly2d/shape.h
Normal file
51
util/coly2d/shape.h
Normal file
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "util/math/vector.h"
|
||||
|
||||
typedef enum {
|
||||
COLY2D_SHAPE_TYPE_LINE,
|
||||
COLY2D_SHAPE_TYPE_RECT,
|
||||
COLY2D_SHAPE_TYPE_TRIANGLE,
|
||||
COLY2D_SHAPE_TYPE_ELLIPSE,
|
||||
} coly2d_shape_type_t;
|
||||
|
||||
typedef struct {
|
||||
coly2d_shape_type_t type;
|
||||
vec2_t size;
|
||||
float angle;
|
||||
} coly2d_shape_t;
|
||||
|
||||
#define coly2d_shape_line(ed, a) ((coly2d_shape_t) { \
|
||||
.type = COLY2D_SHAPE_TYPE_LINE, \
|
||||
.size = ed, \
|
||||
.angle = a, \
|
||||
})
|
||||
#define coly2d_shape_rect(sz, a) ((coly2d_shape_t) { \
|
||||
.type = COLY2D_SHAPE_TYPE_RECT, \
|
||||
.size = sz, \
|
||||
.angle = a, \
|
||||
})
|
||||
#define coly2d_shape_triangle(sz, a) ((coly2d_shape_t) { \
|
||||
.type = COLY2D_SHAPE_TYPE_TRIANGLE, \
|
||||
.size = sz, \
|
||||
.angle = a, \
|
||||
})
|
||||
#define coly2d_shape_ellipse(sz, a) ((coly2d_shape_t) { \
|
||||
.type = COLY2D_SHAPE_TYPE_ELLIPSE, \
|
||||
.size = sz, \
|
||||
.angle = a, \
|
||||
})
|
||||
|
||||
bool
|
||||
coly2d_shape_valid(
|
||||
const coly2d_shape_t* shape
|
||||
);
|
||||
|
||||
bool
|
||||
coly2d_shape_hittest_lineseg(
|
||||
const coly2d_shape_t* shape,
|
||||
const vec2_t* st,
|
||||
const vec2_t* ed
|
||||
);
|
11
util/container/CMakeLists.txt
Normal file
11
util/container/CMakeLists.txt
Normal file
@@ -0,0 +1,11 @@
|
||||
add_library(container
|
||||
array.c
|
||||
)
|
||||
target_link_libraries(container memory)
|
||||
|
||||
if (BUILD_TESTING)
|
||||
add_executable(container-test test.c)
|
||||
target_link_libraries(container-test container)
|
||||
|
||||
add_test(container-test container-test)
|
||||
endif()
|
90
util/container/array.c
Normal file
90
util/container/array.c
Normal file
@@ -0,0 +1,90 @@
|
||||
#include "./array.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "util/memory/memory.h"
|
||||
|
||||
typedef struct {
|
||||
size_t length;
|
||||
size_t reserved;
|
||||
} container_array_header_t;
|
||||
|
||||
void container_array_delete(void* array) {
|
||||
if (array == NULL) return;
|
||||
memory_delete(array - sizeof(container_array_header_t));
|
||||
}
|
||||
|
||||
void container_array_reserve_(void** array, size_t elmlen, size_t len) {
|
||||
assert(array != NULL);
|
||||
|
||||
if (len == 0) return;
|
||||
|
||||
container_array_header_t* ptr;
|
||||
if (*array == NULL) {
|
||||
ptr = memory_new(sizeof(*ptr) + len*elmlen);
|
||||
*ptr = (typeof(*ptr)) {
|
||||
.length = 0,
|
||||
.reserved = len,
|
||||
};
|
||||
} else {
|
||||
ptr = *array - sizeof(*ptr);
|
||||
if (ptr->reserved >= len) return;
|
||||
|
||||
ptr = memory_resize(ptr, sizeof(*ptr) + len*elmlen);
|
||||
ptr->reserved = len;
|
||||
}
|
||||
*array = ptr + 1;
|
||||
}
|
||||
|
||||
void container_array_resize_(void** array, size_t elmlen, size_t len) {
|
||||
assert(array != NULL);
|
||||
|
||||
container_array_reserve_(array, elmlen, len);
|
||||
if (*array == NULL) return;
|
||||
|
||||
container_array_header_t* ptr = *array - sizeof(*ptr);
|
||||
ptr->length = len;
|
||||
}
|
||||
|
||||
void container_array_insert_(void** array, size_t elmlen, size_t index) {
|
||||
assert(array != NULL);
|
||||
|
||||
const size_t len = container_array_get_length(*array) + 1;
|
||||
assert(index < len);
|
||||
container_array_resize_(array, elmlen, len);
|
||||
|
||||
uint8_t* src = *array + (index)*elmlen;
|
||||
memmove(src + elmlen, src, (len-index-1)*elmlen);
|
||||
}
|
||||
|
||||
void container_array_remove_(void** array, size_t elmlen, size_t index) {
|
||||
const size_t oldlen = container_array_get_length(*array);
|
||||
assert(index < oldlen);
|
||||
|
||||
const size_t len = oldlen-1;
|
||||
|
||||
uint8_t* dst = *array + index*elmlen;
|
||||
memmove(dst, dst + elmlen, (len-index)*elmlen);
|
||||
container_array_resize_(array, elmlen, len);
|
||||
}
|
||||
|
||||
void* container_array_duplicate_(const void* array, size_t elmlen) {
|
||||
if (array == NULL) return NULL;
|
||||
const container_array_header_t* ptr = array - sizeof(*ptr);
|
||||
|
||||
const size_t sz = sizeof(*ptr) + ptr->length*elmlen;
|
||||
|
||||
container_array_header_t* ptrdup = memory_new(sz);
|
||||
memcpy(ptrdup, ptr, sz);
|
||||
ptrdup->reserved = ptr->length;
|
||||
return (void*) (ptrdup + 1);
|
||||
}
|
||||
|
||||
size_t container_array_get_length_(const void* array) {
|
||||
if (array == NULL) return 0;
|
||||
const container_array_header_t* ptr = array - sizeof(*ptr);
|
||||
return ptr->length;
|
||||
}
|
31
util/container/array.h
Normal file
31
util/container/array.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#define CONTAINER_ARRAY
|
||||
|
||||
void container_array_delete(void* array);
|
||||
|
||||
#define container_array_reserve(array, len) \
|
||||
container_array_reserve_((void**) &array, sizeof(*array), len)
|
||||
void container_array_reserve_(void** array, size_t elmlen, size_t len);
|
||||
|
||||
#define container_array_resize(array, len) \
|
||||
container_array_resize_((void**) &array, sizeof(*array), len)
|
||||
void container_array_resize_(void** array, size_t elmlen, size_t len);
|
||||
|
||||
#define container_array_insert(array, index) \
|
||||
container_array_insert_((void**) &array, sizeof(*array), index)
|
||||
void container_array_insert_(void** array, size_t elmlen, size_t index);
|
||||
|
||||
#define container_array_remove(array, index) \
|
||||
container_array_remove_((void**) &array, sizeof(*array), index)
|
||||
void container_array_remove_(void** array, size_t elmlen, size_t index);
|
||||
|
||||
#define container_array_duplicate(array) \
|
||||
(typeof(array)) container_array_duplicate_( \
|
||||
(const void*) array, sizeof(*array))
|
||||
void* container_array_duplicate_(const void* array, size_t elmlen);
|
||||
|
||||
#define container_array_get_length(array) container_array_get_length_(array)
|
||||
size_t container_array_get_length_(const void* array);
|
48
util/container/test.c
Normal file
48
util/container/test.c
Normal file
@@ -0,0 +1,48 @@
|
||||
#undef NDEBUG
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "./array.h"
|
||||
static void test_array() {
|
||||
int32_t* numbers = NULL;
|
||||
assert(container_array_get_length(numbers) == 0);
|
||||
|
||||
container_array_reserve(numbers, 10);
|
||||
for (int32_t i = 50; i < 100; ++i) {
|
||||
container_array_insert(numbers, i-50);
|
||||
numbers[i-50] = i;
|
||||
}
|
||||
container_array_reserve(numbers, 10);
|
||||
for (int32_t i = 0; i < 50; ++i) {
|
||||
container_array_insert(numbers, i);
|
||||
numbers[i] = i;
|
||||
}
|
||||
for (int32_t i = 0; i < 100; ++i) {
|
||||
assert(numbers[i] == i);
|
||||
}
|
||||
|
||||
for (int32_t i = 99; i >= 0; i-=2) {
|
||||
container_array_remove(numbers, i);
|
||||
}
|
||||
for (int32_t i = 0; i < 100; i+=2) {
|
||||
assert(numbers[i/2] == i);
|
||||
}
|
||||
assert(container_array_get_length(numbers) == 50);
|
||||
|
||||
container_array_resize(numbers, 25);
|
||||
assert(container_array_get_length(numbers) == 25);
|
||||
|
||||
int32_t* dup = container_array_duplicate(numbers);
|
||||
for (int32_t i = 0; i < 25; ++i) {
|
||||
assert(numbers[i] == dup[i]);
|
||||
}
|
||||
container_array_delete(dup);
|
||||
|
||||
container_array_delete(numbers);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
test_array();
|
||||
return 0;
|
||||
}
|
3
util/conv/CMakeLists.txt
Normal file
3
util/conv/CMakeLists.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
add_library(conv
|
||||
charcode.c
|
||||
)
|
36
util/conv/charcode.c
Normal file
36
util/conv/charcode.c
Normal file
@@ -0,0 +1,36 @@
|
||||
#include "./charcode.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
size_t conv_charcode_utf8_to_utf32(uint32_t* c, const char* s, size_t len) {
|
||||
assert(c != NULL);
|
||||
assert(s != NULL);
|
||||
|
||||
if ((*s & 0x80) == 0x00) {
|
||||
if (len < 1) return 0;
|
||||
*c = s[0];
|
||||
return 1;
|
||||
}
|
||||
if ((*s & 0xE0) == 0xC0) {
|
||||
if (len < 2) return 0;
|
||||
*c = (s[0] & 0x1F) << 6 | (s[1] & 0x3F);
|
||||
return 2;
|
||||
}
|
||||
if ((*s & 0xF0) == 0xE0) {
|
||||
if (len < 3) return 0;
|
||||
*c = (s[0] & 0x0F) << 12 | (s[1] & 0x3F) << 6 | (s[2] & 0x3F);
|
||||
return 3;
|
||||
}
|
||||
if ((*s & 0xF8) == 0xF0) {
|
||||
if (len < 4) return 0;
|
||||
*c =
|
||||
(s[0] & 0x07) << 18 |
|
||||
(s[1] & 0x3F) << 12 |
|
||||
(s[2] & 0x3F) << 6 |
|
||||
(s[3] & 0x3F);
|
||||
return 4;
|
||||
}
|
||||
return 0;
|
||||
}
|
11
util/conv/charcode.h
Normal file
11
util/conv/charcode.h
Normal file
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
size_t
|
||||
conv_charcode_utf8_to_utf32(
|
||||
uint32_t* c,
|
||||
const char* s,
|
||||
size_t len
|
||||
);
|
11
util/dictres/CMakeLists.txt
Normal file
11
util/dictres/CMakeLists.txt
Normal file
@@ -0,0 +1,11 @@
|
||||
add_library(dictres
|
||||
dictres.c
|
||||
)
|
||||
target_link_libraries(dictres
|
||||
chaos
|
||||
)
|
||||
|
||||
if (BUILD_TESTING)
|
||||
add_executable(dictres-test test.c)
|
||||
target_link_libraries(dictres-test dictres)
|
||||
endif()
|
87
util/dictres/dictres.c
Normal file
87
util/dictres/dictres.c
Normal file
@@ -0,0 +1,87 @@
|
||||
#include "./dictres.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "util/chaos/abchash.h"
|
||||
|
||||
static void dictres_calculate_hash_(dictres_item_t* items, size_t length) {
|
||||
assert(items != NULL || length == 0);
|
||||
|
||||
for (size_t i = 0; i < length; ++i) {
|
||||
dictres_item_t* item = &items[i];
|
||||
|
||||
const size_t len = strlen(item->key);
|
||||
if (!chaos_abchash_validate(item->key, len)) {
|
||||
fprintf(stderr, "dictress: invalid key, '%s'\n", item->key);
|
||||
abort();
|
||||
}
|
||||
item->hash = chaos_abchash(item->key, len);
|
||||
}
|
||||
}
|
||||
|
||||
static void dictres_sort_(dictres_item_t* items, size_t length) {
|
||||
assert(items != NULL || length == 0);
|
||||
|
||||
for (size_t i = 0; i < length; ++i) {
|
||||
for (size_t j = 1; j < length-i; ++j) {
|
||||
if (items[j-1].hash > items[j].hash) {
|
||||
/* Speed of swapping can be improved but simple is best. :) */
|
||||
const dictres_item_t temp = items[j-1];
|
||||
items[j-1] = items[j];
|
||||
items[j] = temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void dictres_validate_(dictres_item_t* items, size_t length) {
|
||||
assert(items != NULL || length == 0);
|
||||
|
||||
for (size_t i = 1; i < length; ++i) {
|
||||
if (items[i-1].hash == items[i].hash) {
|
||||
fprintf(stderr,
|
||||
"dictres: detected hash collision between '%s' and '%s'\n",
|
||||
items[i-1].key, items[i].key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void dictres_optimize_(dictres_item_t* items, size_t length) {
|
||||
assert(items != NULL || length == 0);
|
||||
|
||||
dictres_calculate_hash_(items, length);
|
||||
dictres_sort_(items, length);
|
||||
dictres_validate_(items, length);
|
||||
}
|
||||
|
||||
const char* dictres_find_(
|
||||
const dictres_item_t* items, size_t length, const char* key) {
|
||||
assert(items != NULL || length == 0);
|
||||
|
||||
const size_t keylen = strlen(key);
|
||||
assert(chaos_abchash_validate(key, keylen));
|
||||
|
||||
const uint64_t hash = chaos_abchash(key, keylen);
|
||||
|
||||
size_t l = 0, r = length-1;
|
||||
while (l <= r && r < length) {
|
||||
const size_t i = (l+r)/2;
|
||||
|
||||
if (items[i].hash == hash) {
|
||||
return items[i].value;
|
||||
}
|
||||
if (items[i].hash < hash) {
|
||||
l = i+1;
|
||||
} else {
|
||||
if (i == 0) break;
|
||||
r = i-1;
|
||||
}
|
||||
}
|
||||
fprintf(stderr, "dictres: cannot find key, '%s'\n", key);
|
||||
abort();
|
||||
}
|
27
util/dictres/dictres.h
Normal file
27
util/dictres/dictres.h
Normal file
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct {
|
||||
const char* key;
|
||||
const char* value;
|
||||
uint64_t hash;
|
||||
} dictres_item_t;
|
||||
|
||||
void
|
||||
dictres_optimize_(
|
||||
dictres_item_t* items,
|
||||
size_t length
|
||||
);
|
||||
#define dictres_optimize(items) \
|
||||
dictres_optimize_(items, sizeof(items)/sizeof(items[0]))
|
||||
|
||||
const char*
|
||||
dictres_find_(
|
||||
const dictres_item_t* items,
|
||||
size_t length,
|
||||
const char* key
|
||||
);
|
||||
#define dictres_find(items, key) \
|
||||
dictres_find_(items, sizeof(items)/sizeof(items[0]), key)
|
27
util/dictres/test.c
Normal file
27
util/dictres/test.c
Normal file
@@ -0,0 +1,27 @@
|
||||
#undef NDEBUG
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "./dictres.h"
|
||||
|
||||
static dictres_item_t dictres_[] = {
|
||||
{"app_name",
|
||||
"dictres-test"},
|
||||
{"description",
|
||||
"dictres provides a static dictionary type which finds value from key fast."},
|
||||
{"bye",
|
||||
"see you!"},
|
||||
};
|
||||
|
||||
int main(void) {
|
||||
dictres_optimize(dictres_);
|
||||
|
||||
printf("%s\n", dictres_find(dictres_, "app_name"));
|
||||
printf("====\n");
|
||||
|
||||
printf("%s\n", dictres_find(dictres_, "description"));
|
||||
printf("%s\n", dictres_find(dictres_, "bye"));
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
14
util/gleasy/CMakeLists.txt
Normal file
14
util/gleasy/CMakeLists.txt
Normal file
@@ -0,0 +1,14 @@
|
||||
add_library(gleasy
|
||||
atlas.c
|
||||
framebuffer.c
|
||||
program.c
|
||||
shader.c
|
||||
)
|
||||
target_link_libraries(gleasy
|
||||
GLEW::GLEW
|
||||
OpenGL::GL
|
||||
|
||||
container
|
||||
math
|
||||
memory
|
||||
)
|
194
util/gleasy/atlas.c
Normal file
194
util/gleasy/atlas.c
Normal file
@@ -0,0 +1,194 @@
|
||||
#include "./atlas.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <GL/glew.h>
|
||||
|
||||
#include "util/container/array.h"
|
||||
#include "util/math/algorithm.h"
|
||||
#include "util/memory/memory.h"
|
||||
|
||||
#include "./misc.h"
|
||||
#include "./texture.h"
|
||||
|
||||
struct gleasy_atlas_t {
|
||||
int32_t width;
|
||||
int32_t height;
|
||||
|
||||
int32_t consumed_y;
|
||||
int32_t consumed_x;
|
||||
|
||||
int32_t line_height;
|
||||
|
||||
gleasy_texture_2d_t tex;
|
||||
|
||||
CONTAINER_ARRAY uint8_t* resize_buffer;
|
||||
};
|
||||
|
||||
static void gleasy_atlas_resize_bitmap_(
|
||||
gleasy_atlas_t* atlas,
|
||||
gleasy_atlas_bitmap_t* out,
|
||||
const gleasy_atlas_bitmap_t* in) {
|
||||
assert(atlas != NULL);
|
||||
assert(out != NULL);
|
||||
assert(in != NULL);
|
||||
|
||||
*out = (typeof(*out)) {
|
||||
.width = math_int32_next_power2(MATH_MAX(in->width, 4)),
|
||||
.height = math_int32_next_power2(MATH_MAX(in->height, 4)),
|
||||
.format = in->format,
|
||||
.type = in->type,
|
||||
.buffer = in->buffer,
|
||||
};
|
||||
if (out->width == in->width && out->height == in->height) return;
|
||||
|
||||
const size_t type = GLEASY_GET_BYTE_SIZE_OF_TYPE(out->type);
|
||||
const size_t fmt = GLEASY_GET_CHANNELS_OF_TEXTURE_FORMAT(out->format);
|
||||
|
||||
const size_t pixel = type*fmt;
|
||||
assert(pixel > 0);
|
||||
|
||||
container_array_resize(atlas->resize_buffer, out->width*out->height*pixel);
|
||||
out->buffer = atlas->resize_buffer;
|
||||
|
||||
const int32_t ymax = out->height * pixel;
|
||||
const int32_t xmax = out->width * pixel;
|
||||
|
||||
const uint8_t* src = in->buffer;
|
||||
uint8_t* dst = atlas->resize_buffer;
|
||||
for (int32_t y = 0; y < ymax; ++y) {
|
||||
for (int32_t x = 0; x < xmax; ++x) {
|
||||
if (x < in->width && y < in->height) {
|
||||
*dst = *(src++);
|
||||
} else {
|
||||
*dst = 0;
|
||||
}
|
||||
++dst;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool gleasy_atlas_allocate_area_(
|
||||
gleasy_atlas_t* atlas,
|
||||
int32_t* x,
|
||||
int32_t* y,
|
||||
int32_t width,
|
||||
int32_t height,
|
||||
int32_t actual_width,
|
||||
int32_t actual_height) {
|
||||
assert(atlas != NULL);
|
||||
assert(x != NULL);
|
||||
assert(y != NULL);
|
||||
assert(width > 0);
|
||||
assert(height > 0);
|
||||
assert(actual_width > 0);
|
||||
assert(actual_height > 0);
|
||||
|
||||
if (atlas->consumed_x + actual_width > atlas->width) {
|
||||
atlas->consumed_x = 0;
|
||||
atlas->consumed_y += atlas->line_height+1;
|
||||
atlas->line_height = 0;
|
||||
}
|
||||
if (atlas->consumed_y + actual_height > atlas->height) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*x = atlas->consumed_x;
|
||||
*y = atlas->consumed_y;
|
||||
|
||||
atlas->consumed_x += width+1;
|
||||
atlas->line_height = MATH_MAX(atlas->line_height, height);
|
||||
return true;
|
||||
}
|
||||
|
||||
gleasy_atlas_t* gleasy_atlas_new(
|
||||
GLenum format, int32_t width, int32_t height, bool aa) {
|
||||
assert(width > 0);
|
||||
assert(height > 0);
|
||||
|
||||
gleasy_atlas_t* atlas = memory_new(sizeof(*atlas));
|
||||
*atlas = (typeof(*atlas)) {
|
||||
.width = math_int32_next_power2(width),
|
||||
.height = math_int32_next_power2(height),
|
||||
};
|
||||
|
||||
glGenTextures(1, &atlas->tex);
|
||||
glBindTexture(GL_TEXTURE_2D, atlas->tex);
|
||||
|
||||
const GLenum filter = aa? GL_LINEAR: GL_NEAREST;
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);
|
||||
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, format,
|
||||
atlas->width, atlas->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
|
||||
|
||||
assert(glGetError() == GL_NO_ERROR);
|
||||
|
||||
container_array_reserve(atlas->resize_buffer, width*height*4);
|
||||
return atlas;
|
||||
}
|
||||
|
||||
void gleasy_atlas_delete(gleasy_atlas_t* atlas) {
|
||||
if (atlas == NULL) return;
|
||||
|
||||
glDeleteTextures(1, &atlas->tex);
|
||||
|
||||
container_array_delete(atlas->resize_buffer);
|
||||
|
||||
memory_delete(atlas);
|
||||
}
|
||||
|
||||
void gleasy_atlas_clear(gleasy_atlas_t* atlas) {
|
||||
assert(atlas != NULL);
|
||||
|
||||
atlas->consumed_y = 0;
|
||||
atlas->consumed_x = 0;
|
||||
|
||||
atlas->line_height = 0;
|
||||
}
|
||||
|
||||
bool gleasy_atlas_add(
|
||||
gleasy_atlas_t* atlas,
|
||||
gleasy_atlas_geometry_t* geo,
|
||||
const gleasy_atlas_bitmap_t* bitmap) {
|
||||
assert(atlas != NULL);
|
||||
assert(geo != NULL);
|
||||
assert(bitmap != NULL);
|
||||
|
||||
gleasy_atlas_bitmap_t resized;
|
||||
gleasy_atlas_resize_bitmap_(atlas, &resized, bitmap);
|
||||
|
||||
int32_t x, y;
|
||||
if (!gleasy_atlas_allocate_area_(atlas,
|
||||
&x, &y, bitmap->width, bitmap->height, resized.width, resized.height)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*geo = (typeof(*geo)) {
|
||||
.left = x*1.0f / atlas->width,
|
||||
.right = (x+bitmap->width)*1.0f / atlas->width,
|
||||
.top = y*1.0f / atlas->height,
|
||||
.bottom = (y+bitmap->height)*1.0f / atlas->height,
|
||||
};
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, atlas->tex);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, x, y,
|
||||
resized.width,
|
||||
resized.height,
|
||||
resized.format,
|
||||
resized.type,
|
||||
resized.buffer);
|
||||
assert(glGetError() == GL_NO_ERROR);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
gleasy_texture_2d_t gleasy_atlas_get_texture(const gleasy_atlas_t* atlas) {
|
||||
assert(atlas != NULL);
|
||||
|
||||
return atlas->tex;
|
||||
}
|
56
util/gleasy/atlas.h
Normal file
56
util/gleasy/atlas.h
Normal file
@@ -0,0 +1,56 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <GL/glew.h>
|
||||
|
||||
#include "./texture.h"
|
||||
|
||||
struct gleasy_atlas_t;
|
||||
typedef struct gleasy_atlas_t gleasy_atlas_t;
|
||||
|
||||
typedef struct {
|
||||
float left;
|
||||
float right;
|
||||
float top;
|
||||
float bottom;
|
||||
} gleasy_atlas_geometry_t;
|
||||
|
||||
typedef struct {
|
||||
int32_t width;
|
||||
int32_t height;
|
||||
GLenum format;
|
||||
GLenum type;
|
||||
const uint8_t* buffer;
|
||||
} gleasy_atlas_bitmap_t;
|
||||
|
||||
gleasy_atlas_t*
|
||||
gleasy_atlas_new(
|
||||
GLenum format,
|
||||
int32_t width,
|
||||
int32_t height,
|
||||
bool aa
|
||||
);
|
||||
|
||||
void
|
||||
gleasy_atlas_delete(
|
||||
gleasy_atlas_t* atlas
|
||||
);
|
||||
|
||||
void
|
||||
gleasy_atlas_clear(
|
||||
gleasy_atlas_t* atlas
|
||||
);
|
||||
|
||||
bool
|
||||
gleasy_atlas_add(
|
||||
gleasy_atlas_t* atlas,
|
||||
gleasy_atlas_geometry_t* geo,
|
||||
const gleasy_atlas_bitmap_t* bitmap
|
||||
);
|
||||
|
||||
gleasy_texture_2d_t
|
||||
gleasy_atlas_get_texture(
|
||||
const gleasy_atlas_t* atlas
|
||||
);
|
7
util/gleasy/buffer.h
Normal file
7
util/gleasy/buffer.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <GL/glew.h>
|
||||
|
||||
typedef GLuint gleasy_buffer_array_t;
|
||||
typedef GLuint gleasy_buffer_element_array_t;
|
||||
typedef GLuint gleasy_buffer_uniform_t;
|
96
util/gleasy/framebuffer.c
Normal file
96
util/gleasy/framebuffer.c
Normal file
@@ -0,0 +1,96 @@
|
||||
#include "./framebuffer.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <GL/glew.h>
|
||||
|
||||
#include "./texture.h"
|
||||
|
||||
void gleasy_framebuffer_initialize(
|
||||
gleasy_framebuffer_t* fb, int32_t width, int32_t height, int32_t samples) {
|
||||
assert(fb != NULL);
|
||||
assert(width > 0);
|
||||
assert(height > 0);
|
||||
assert(0 < samples && samples < GL_MAX_SAMPLES);
|
||||
|
||||
*fb = (typeof(*fb)) {
|
||||
.width = width,
|
||||
.height = height,
|
||||
};
|
||||
|
||||
glGenRenderbuffers(1, &fb->colorbuf_msaa);
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, fb->colorbuf_msaa);
|
||||
glRenderbufferStorageMultisample(
|
||||
GL_RENDERBUFFER, samples, GL_RGBA, width, height);
|
||||
assert(glGetError() == GL_NO_ERROR);
|
||||
|
||||
glGenTextures(1, &fb->colorbuf);
|
||||
glBindTexture(GL_TEXTURE_2D, fb->colorbuf);
|
||||
glTexImage2D(GL_TEXTURE_2D,
|
||||
0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
assert(glGetError() == GL_NO_ERROR);
|
||||
|
||||
GLuint fbo[2];
|
||||
glGenFramebuffers(2, fbo);
|
||||
fb->id_msaa = fbo[0];
|
||||
fb->id = fbo[1];
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fb->id_msaa);
|
||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER,
|
||||
GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, fb->colorbuf_msaa);
|
||||
assert(glGetError() == GL_NO_ERROR);
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fb->id);
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER,
|
||||
GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fb->colorbuf, 0);
|
||||
assert(glGetError() == GL_NO_ERROR);
|
||||
}
|
||||
|
||||
void gleasy_framebuffer_deinitialize(gleasy_framebuffer_t* fb) {
|
||||
assert(fb != NULL);
|
||||
|
||||
GLuint fbo[] = {fb->id_msaa, fb->id};
|
||||
glDeleteFramebuffers(2, fbo);
|
||||
|
||||
glDeleteTextures(1, &fb->colorbuf);
|
||||
|
||||
glDeleteRenderbuffers(1, &fb->colorbuf_msaa);
|
||||
}
|
||||
|
||||
void gleasy_framebuffer_bind(const gleasy_framebuffer_t* fb) {
|
||||
assert(fb != NULL);
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fb->id_msaa);
|
||||
|
||||
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
|
||||
fprintf(stderr, "gleasy: framebuffer is in invalid state\n");
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
void gleasy_framebuffer_flush(const gleasy_framebuffer_t* fb) {
|
||||
assert(fb != NULL);
|
||||
|
||||
gleasy_framebuffer_flush_to_other(fb, fb->id);
|
||||
}
|
||||
|
||||
void gleasy_framebuffer_flush_to_other(const gleasy_framebuffer_t* fb, GLuint id) {
|
||||
assert(fb != NULL);
|
||||
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, id);
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, fb->id_msaa);
|
||||
glBlitFramebuffer(
|
||||
0, 0, fb->width, fb->height,
|
||||
0, 0, fb->width, fb->height,
|
||||
GL_COLOR_BUFFER_BIT,
|
||||
GL_NEAREST);
|
||||
assert(glGetError() == GL_NO_ERROR);
|
||||
}
|
47
util/gleasy/framebuffer.h
Normal file
47
util/gleasy/framebuffer.h
Normal file
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <GL/glew.h>
|
||||
|
||||
#include "./texture.h"
|
||||
|
||||
typedef struct {
|
||||
int32_t width;
|
||||
int32_t height;
|
||||
|
||||
GLuint id_msaa;
|
||||
GLuint colorbuf_msaa;
|
||||
|
||||
GLuint id;
|
||||
gleasy_texture_2d_t colorbuf;
|
||||
} gleasy_framebuffer_t;
|
||||
|
||||
void
|
||||
gleasy_framebuffer_initialize(
|
||||
gleasy_framebuffer_t* fb,
|
||||
int32_t width,
|
||||
int32_t height,
|
||||
int32_t samples
|
||||
);
|
||||
|
||||
void
|
||||
gleasy_framebuffer_deinitialize(
|
||||
gleasy_framebuffer_t* fb
|
||||
);
|
||||
|
||||
void
|
||||
gleasy_framebuffer_bind(
|
||||
const gleasy_framebuffer_t* fb
|
||||
);
|
||||
|
||||
void
|
||||
gleasy_framebuffer_flush(
|
||||
const gleasy_framebuffer_t* fb
|
||||
);
|
||||
|
||||
void
|
||||
gleasy_framebuffer_flush_to_other(
|
||||
const gleasy_framebuffer_t* fb,
|
||||
GLuint id
|
||||
);
|
18
util/gleasy/misc.h
Normal file
18
util/gleasy/misc.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include <GL/glew.h>
|
||||
|
||||
#define GLEASY_GET_BYTE_SIZE_OF_TYPE(t) ( \
|
||||
(t) == GL_UNSIGNED_BYTE? 1: \
|
||||
(t) == GL_BYTE? 1: \
|
||||
(t) == GL_SHORT? 2: \
|
||||
(t) == GL_UNSIGNED_SHORT? 2: \
|
||||
(t) == GL_INT? 4: \
|
||||
(t) == GL_UNSIGNED_INT? 4: \
|
||||
(t) == GL_FLOAT? 4: 0)
|
||||
|
||||
#define GLEASY_GET_CHANNELS_OF_TEXTURE_FORMAT(f) ( \
|
||||
(f) == GL_RGBA? 4: \
|
||||
(f) == GL_RGB? 3: \
|
||||
(f) == GL_RG? 2: \
|
||||
(f) == GL_RED? 1: 0)
|
66
util/gleasy/program.c
Normal file
66
util/gleasy/program.c
Normal file
@@ -0,0 +1,66 @@
|
||||
#include "./program.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <GL/glew.h>
|
||||
|
||||
#include "util/math/algorithm.h"
|
||||
|
||||
#include "./shader.h"
|
||||
|
||||
gleasy_program_t gleasy_program_new(
|
||||
const char* header, size_t header_len,
|
||||
const char* vsrc, size_t vsrc_len,
|
||||
const char* fsrc, size_t fsrc_len) {
|
||||
assert(header != NULL || header_len == 0);
|
||||
assert(vsrc != NULL || vsrc_len == 0);
|
||||
assert(fsrc != NULL || fsrc_len == 0);
|
||||
|
||||
gleasy_program_t program = glCreateProgram();
|
||||
if (program == 0) {
|
||||
fprintf(stderr, "failed to create program");
|
||||
abort();
|
||||
}
|
||||
|
||||
const gleasy_shader_t vshader =
|
||||
gleasy_shader_new(GL_VERTEX_SHADER, header, header_len, vsrc, vsrc_len);
|
||||
const gleasy_shader_t fshader =
|
||||
gleasy_shader_new(GL_FRAGMENT_SHADER, header, header_len, fsrc, fsrc_len);
|
||||
|
||||
glAttachShader(program, vshader);
|
||||
glDeleteShader(vshader);
|
||||
|
||||
glAttachShader(program, fshader);
|
||||
glDeleteShader(fshader);
|
||||
|
||||
glLinkProgram(program);
|
||||
|
||||
GLint ok;
|
||||
glGetProgramiv(program, GL_LINK_STATUS, &ok);
|
||||
if (ok == GL_FALSE) {
|
||||
char log[1024];
|
||||
const int len =
|
||||
gleasy_program_get_log(program, log, sizeof(log)/sizeof(log[0]));
|
||||
fprintf(stderr, "failed to link program\n%.*s\n", len, log);
|
||||
abort();
|
||||
}
|
||||
return program;
|
||||
}
|
||||
|
||||
size_t gleasy_program_get_log(
|
||||
gleasy_program_t program, char* dst, size_t maxlen) {
|
||||
assert(program != 0);
|
||||
|
||||
GLint len;
|
||||
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &len);
|
||||
if (dst == NULL) return len;
|
||||
|
||||
len = MATH_MIN(len, (GLint) maxlen);
|
||||
if (len == 0) return 0;
|
||||
|
||||
glGetProgramInfoLog(program, len, &len, (GLchar*) dst);
|
||||
return len;
|
||||
}
|
24
util/gleasy/program.h
Normal file
24
util/gleasy/program.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <GL/glew.h>
|
||||
|
||||
typedef GLuint gleasy_program_t;
|
||||
|
||||
gleasy_program_t /* OWNERSHIP */
|
||||
gleasy_program_new(
|
||||
const char* header,
|
||||
size_t header_len,
|
||||
const char* vsrc,
|
||||
size_t vsrc_len,
|
||||
const char* fsrc,
|
||||
size_t fsrc_len
|
||||
);
|
||||
|
||||
size_t
|
||||
gleasy_program_get_log(
|
||||
gleasy_program_t program,
|
||||
char* dst, /* when NULL, returns actual size */
|
||||
size_t maxlen
|
||||
);
|
57
util/gleasy/shader.c
Normal file
57
util/gleasy/shader.c
Normal file
@@ -0,0 +1,57 @@
|
||||
#include "./shader.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <GL/glew.h>
|
||||
|
||||
#include "util/math/algorithm.h"
|
||||
|
||||
gleasy_shader_t gleasy_shader_new(
|
||||
GLenum type,
|
||||
const char* header, size_t header_len,
|
||||
const char* src, size_t src_len) {
|
||||
assert(header != NULL || header_len == 0);
|
||||
assert(src != NULL || src_len == 0);
|
||||
|
||||
const GLuint shader = glCreateShader(type);
|
||||
if (shader == 0) {
|
||||
fprintf(stderr, "failed to create shader\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
const GLchar* srcs[] = { header, src, };
|
||||
const GLint lens[] = { header_len, src_len, };
|
||||
const size_t offset = (header_len == 0? 1: 0);
|
||||
glShaderSource(shader, 2-offset, srcs+offset, lens+offset);
|
||||
glCompileShader(shader);
|
||||
|
||||
GLint ok;
|
||||
glGetShaderiv(shader, GL_COMPILE_STATUS, &ok);
|
||||
if (ok == GL_FALSE) {
|
||||
char log[1024];
|
||||
const int loglen =
|
||||
gleasy_shader_get_log(shader, log, sizeof(log)/sizeof(log[0]));
|
||||
fprintf(stderr, "failed to compile shader\n%.*s\n", loglen, log);
|
||||
abort();
|
||||
}
|
||||
return shader;
|
||||
}
|
||||
|
||||
size_t gleasy_shader_get_log(
|
||||
gleasy_shader_t shader, char* dst, size_t maxlen) {
|
||||
assert(shader != 0);
|
||||
|
||||
GLint len;
|
||||
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &len);
|
||||
if (dst == NULL) return len;
|
||||
|
||||
len = MATH_MIN(len, (GLint) maxlen);
|
||||
if (len == 0) return 0;
|
||||
|
||||
glGetShaderInfoLog(shader, len, &len, (GLchar*) dst);
|
||||
return len;
|
||||
}
|
23
util/gleasy/shader.h
Normal file
23
util/gleasy/shader.h
Normal file
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <GL/glew.h>
|
||||
|
||||
typedef GLuint gleasy_shader_t;
|
||||
|
||||
gleasy_shader_t /* OWNERSHIP */
|
||||
gleasy_shader_new(
|
||||
GLenum type,
|
||||
const char* header,
|
||||
size_t header_len,
|
||||
const char* src,
|
||||
size_t src_len
|
||||
);
|
||||
|
||||
size_t
|
||||
gleasy_shader_get_log(
|
||||
gleasy_shader_t shader,
|
||||
char* dst, /* when NULL, returns actual size */
|
||||
size_t maxlen
|
||||
);
|
5
util/gleasy/texture.h
Normal file
5
util/gleasy/texture.h
Normal file
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <GL/glew.h>
|
||||
|
||||
typedef GLuint gleasy_texture_2d_t;
|
33
util/glyphas/CMakeLists.txt
Normal file
33
util/glyphas/CMakeLists.txt
Normal file
@@ -0,0 +1,33 @@
|
||||
add_library(glyphas
|
||||
aligner.c
|
||||
block.c
|
||||
cache.c
|
||||
context.c
|
||||
drawer.c
|
||||
face.c
|
||||
glyph.c
|
||||
)
|
||||
target_any_sources(glyphas
|
||||
drawer.vshader
|
||||
drawer.fshader
|
||||
)
|
||||
target_link_libraries(glyphas
|
||||
Freetype::Freetype
|
||||
GLEW::GLEW
|
||||
OpenGL::GL
|
||||
|
||||
chaos
|
||||
container
|
||||
conv
|
||||
gleasy
|
||||
math
|
||||
memory
|
||||
)
|
||||
|
||||
if (BUILD_TESTING)
|
||||
add_executable(glyphas-test test.c)
|
||||
target_link_libraries(glyphas-test
|
||||
SDL2::SDL2
|
||||
|
||||
glyphas)
|
||||
endif()
|
4
util/glyphas/README.md
Normal file
4
util/glyphas/README.md
Normal file
@@ -0,0 +1,4 @@
|
||||
glyphas
|
||||
====
|
||||
|
||||
GLYPH texture atlAS library wrapping FreeType
|
78
util/glyphas/aligner.c
Normal file
78
util/glyphas/aligner.c
Normal file
@@ -0,0 +1,78 @@
|
||||
#include "./aligner.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "util/gleasy/atlas.h"
|
||||
#include "util/math/algorithm.h"
|
||||
|
||||
#include "./cache.h"
|
||||
|
||||
void glyphas_aligner_initialize(
|
||||
glyphas_aligner_t* aligner,
|
||||
glyphas_aligner_direction_t dir,
|
||||
int32_t lineheight,
|
||||
int32_t maxpos) {
|
||||
assert(aligner != NULL);
|
||||
assert(maxpos > 0);
|
||||
|
||||
*aligner = (typeof(*aligner)) {
|
||||
.dir = dir,
|
||||
.lineheight = lineheight,
|
||||
.maxpos = maxpos,
|
||||
.line = lineheight,
|
||||
};
|
||||
}
|
||||
|
||||
void glyphas_aligner_deinitialize(glyphas_aligner_t* aligner) {
|
||||
assert(aligner != NULL);
|
||||
|
||||
}
|
||||
|
||||
void glyphas_aligner_reset(glyphas_aligner_t* aligner) {
|
||||
assert(aligner != NULL);
|
||||
|
||||
aligner->line = aligner->lineheight;
|
||||
aligner->pos = 0;
|
||||
}
|
||||
|
||||
void glyphas_aligner_push_character(
|
||||
glyphas_aligner_t* aligner,
|
||||
int32_t* x,
|
||||
int32_t* y,
|
||||
const glyphas_cache_glyph_t* g) {
|
||||
assert(aligner != NULL);
|
||||
assert(x != NULL);
|
||||
assert(y != NULL);
|
||||
assert(g != NULL);
|
||||
|
||||
switch (aligner->dir) {
|
||||
case GLYPHAS_ALIGNER_DIRECTION_HORIZONTAL:
|
||||
if (aligner->pos + g->hmetrics.advance > aligner->maxpos) {
|
||||
glyphas_aligner_break_line(aligner);
|
||||
}
|
||||
*x = aligner->pos + g->hmetrics.bear_x;
|
||||
*y = g->hmetrics.bear_y + aligner->line;
|
||||
aligner->pos += g->hmetrics.advance;
|
||||
break;
|
||||
case GLYPHAS_ALIGNER_DIRECTION_VERTICAL:
|
||||
if (aligner->pos - g->vmetrics.advance < -aligner->maxpos) {
|
||||
glyphas_aligner_break_line(aligner);
|
||||
}
|
||||
*x = aligner->pos - g->vmetrics.bear_x + aligner->line;
|
||||
*y = -g->vmetrics.bear_y;
|
||||
aligner->pos -= g->vmetrics.advance;
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
void glyphas_aligner_break_line(glyphas_aligner_t* aligner) {
|
||||
assert(aligner != NULL);
|
||||
|
||||
aligner->pos = 0;
|
||||
aligner->line += aligner->lineheight;
|
||||
}
|
52
util/glyphas/aligner.h
Normal file
52
util/glyphas/aligner.h
Normal file
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "./cache.h"
|
||||
|
||||
typedef enum {
|
||||
GLYPHAS_ALIGNER_DIRECTION_HORIZONTAL,
|
||||
GLYPHAS_ALIGNER_DIRECTION_VERTICAL,
|
||||
} glyphas_aligner_direction_t;
|
||||
|
||||
typedef struct {
|
||||
glyphas_aligner_direction_t dir;
|
||||
int32_t lineheight;
|
||||
int32_t maxpos;
|
||||
|
||||
int32_t pos;
|
||||
int32_t line;
|
||||
} glyphas_aligner_t;
|
||||
|
||||
void
|
||||
glyphas_aligner_initialize(
|
||||
glyphas_aligner_t* aligner,
|
||||
glyphas_aligner_direction_t dir,
|
||||
int32_t lineheight,
|
||||
int32_t maxpos
|
||||
);
|
||||
|
||||
void
|
||||
glyphas_aligner_deinitialize(
|
||||
glyphas_aligner_t* aligner
|
||||
);
|
||||
|
||||
void
|
||||
glyphas_aligner_reset(
|
||||
glyphas_aligner_t* aligner
|
||||
);
|
||||
|
||||
void
|
||||
glyphas_aligner_push_character(
|
||||
glyphas_aligner_t* aligner,
|
||||
int32_t* x,
|
||||
int32_t* y,
|
||||
const glyphas_cache_glyph_t* g
|
||||
);
|
||||
|
||||
void
|
||||
glyphas_aligner_break_line(
|
||||
glyphas_aligner_t* aligner
|
||||
);
|
216
util/glyphas/block.c
Normal file
216
util/glyphas/block.c
Normal file
@@ -0,0 +1,216 @@
|
||||
#include "./block.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "util/chaos/xorshift.h"
|
||||
#include "util/conv/charcode.h"
|
||||
#include "util/math/algorithm.h"
|
||||
#include "util/math/vector.h"
|
||||
#include "util/gleasy/atlas.h"
|
||||
#include "util/memory/memory.h"
|
||||
|
||||
#include "./cache.h"
|
||||
|
||||
struct glyphas_block_t {
|
||||
glyphas_aligner_t aligner;
|
||||
|
||||
size_t reserve;
|
||||
size_t length;
|
||||
glyphas_block_item_t items[1];
|
||||
};
|
||||
|
||||
glyphas_block_t* glyphas_block_new(
|
||||
glyphas_aligner_direction_t dir,
|
||||
int32_t lineheight,
|
||||
int32_t maxpos,
|
||||
size_t reserve) {
|
||||
assert(maxpos > 0);
|
||||
assert(reserve > 0);
|
||||
|
||||
glyphas_block_t* block =
|
||||
memory_new(sizeof(*block) + (reserve-1)*sizeof(block->items[0]));
|
||||
*block = (typeof(*block)) {
|
||||
.reserve = reserve,
|
||||
};
|
||||
|
||||
glyphas_aligner_initialize(&block->aligner, dir, lineheight, maxpos);
|
||||
|
||||
return block;
|
||||
}
|
||||
|
||||
void glyphas_block_delete(glyphas_block_t* block) {
|
||||
if (block == NULL) return;
|
||||
|
||||
glyphas_aligner_deinitialize(&block->aligner);
|
||||
|
||||
memory_delete(block);
|
||||
}
|
||||
|
||||
void glyphas_block_clear(glyphas_block_t* block) {
|
||||
assert(block != NULL);
|
||||
|
||||
glyphas_aligner_reset(&block->aligner);
|
||||
block->length = 0;
|
||||
}
|
||||
|
||||
void glyphas_block_add_character(
|
||||
glyphas_block_t* block,
|
||||
const glyphas_cache_glyph_t* glyph,
|
||||
const vec4_t* color) {
|
||||
assert(block != NULL);
|
||||
assert(glyph != NULL);
|
||||
assert(vec4_valid(color));
|
||||
|
||||
if (block->length >= block->reserve) {
|
||||
fprintf(stderr, "glyphas block overflow\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
int32_t x, y;
|
||||
glyphas_aligner_push_character(&block->aligner, &x, &y, glyph);
|
||||
if (glyph->geometry.left >= glyph->geometry.right) return;
|
||||
|
||||
block->items[block->length++] = (glyphas_block_item_t) {
|
||||
.pos = vec2(x, y),
|
||||
.size = vec2(glyph->width, glyph->height),
|
||||
.color = *color,
|
||||
.uv = glyph->geometry,
|
||||
};
|
||||
}
|
||||
|
||||
void glyphas_block_add_characters(
|
||||
glyphas_block_t* block,
|
||||
glyphas_cache_t* cache,
|
||||
const vec4_t* color,
|
||||
const char* str,
|
||||
size_t len) {
|
||||
assert(block != NULL);
|
||||
assert(cache != NULL);
|
||||
assert(vec4_valid(color));
|
||||
|
||||
const char* end = str + len;
|
||||
while (str < end) {
|
||||
uint32_t c;
|
||||
const size_t consumed = conv_charcode_utf8_to_utf32(&c, str, end-str);
|
||||
str += MATH_MAX(consumed, 1);
|
||||
if (consumed == 0) continue;
|
||||
|
||||
if (c == '\n') {
|
||||
glyphas_block_break_line(block);
|
||||
continue;
|
||||
}
|
||||
|
||||
const glyphas_cache_glyph_t* g = glyphas_cache_add_glyph(cache, c);
|
||||
if (g == NULL) continue;
|
||||
|
||||
glyphas_block_add_character(block, g, color);
|
||||
}
|
||||
}
|
||||
|
||||
void glyphas_block_break_line(glyphas_block_t* block) {
|
||||
assert(block != NULL);
|
||||
|
||||
glyphas_aligner_break_line(&block->aligner);
|
||||
}
|
||||
|
||||
void glyphas_block_scale(glyphas_block_t* block, const vec2_t* s) {
|
||||
assert(block != NULL);
|
||||
assert(vec2_valid(s));
|
||||
|
||||
for (size_t i = 0; i < block->length; ++i) {
|
||||
vec2_t* pos = &block->items[i].pos;
|
||||
vec2_t* sz = &block->items[i].size;
|
||||
|
||||
pos->x *= s->x;
|
||||
pos->y *= s->y;
|
||||
sz->x *= s->x;
|
||||
sz->y *= s->y;
|
||||
}
|
||||
}
|
||||
|
||||
void glyphas_block_translate(glyphas_block_t* block, const vec2_t* v) {
|
||||
assert(block != NULL);
|
||||
assert(vec2_valid(v));
|
||||
|
||||
for (size_t i = 0; i < block->length; ++i) {
|
||||
vec2_addeq(&block->items[i].pos, v);
|
||||
}
|
||||
}
|
||||
|
||||
void glyphas_block_set_origin(glyphas_block_t* block, const vec2_t* r) {
|
||||
assert(block !=NULL);
|
||||
assert(vec2_valid(r));
|
||||
|
||||
vec2_t offset, size;
|
||||
glyphas_block_calculate_geometry(block, &offset, &size);
|
||||
|
||||
size.x *= r->x;
|
||||
size.y *= r->y;
|
||||
|
||||
vec2_addeq(&offset, &size);
|
||||
vec2_muleq(&offset, -1);
|
||||
glyphas_block_translate(block, &offset);
|
||||
}
|
||||
|
||||
void glyphas_block_set_alpha(glyphas_block_t* block, float a) {
|
||||
assert(block != NULL);
|
||||
|
||||
for (size_t i = 0; i < block->length; ++i) {
|
||||
block->items[i].color.w = a;
|
||||
}
|
||||
}
|
||||
|
||||
void glyphas_block_make_glitched(glyphas_block_t* block, uint64_t seed) {
|
||||
assert(block != NULL);
|
||||
|
||||
# define randf() (seed = chaos_xorshift(seed), seed%1000/1000.f)
|
||||
|
||||
for (size_t i = 0; i < block->length; ++i) {
|
||||
const vec2_t* sz = &block->items[i].size;
|
||||
|
||||
const vec2_t shift = vec2((randf()-.5f)*sz->x*.5f, (randf()-.5f)*sz->y*.5f);
|
||||
const float scale = randf()*.4f + .8f;
|
||||
|
||||
vec2_addeq(&block->items[i].pos, &shift);
|
||||
vec2_muleq(&block->items[i].size, scale);
|
||||
}
|
||||
|
||||
# undef randf
|
||||
}
|
||||
|
||||
void glyphas_block_calculate_geometry(
|
||||
const glyphas_block_t* block, vec2_t* offset, vec2_t* size) {
|
||||
assert(block != NULL);
|
||||
assert(offset != NULL);
|
||||
assert(size != NULL);
|
||||
|
||||
vec2_t lb = vec2(0, 0), rt = vec2(0, 0);
|
||||
for (size_t i = 0; i < block->length; ++i) {
|
||||
const glyphas_block_item_t* item = &block->items[i];
|
||||
|
||||
const float l = item->pos.x;
|
||||
const float r = l + item->size.x;
|
||||
const float t = item->pos.y;
|
||||
const float b = t - item->size.y;
|
||||
|
||||
lb.x = MATH_MIN(lb.x, l);
|
||||
lb.y = MATH_MIN(lb.y, b);
|
||||
rt.x = MATH_MAX(rt.x, r);
|
||||
rt.y = MATH_MAX(rt.y, t);
|
||||
}
|
||||
*offset = vec2(lb.x, rt.y);
|
||||
vec2_sub(size, &rt, &lb);
|
||||
}
|
||||
|
||||
const glyphas_block_item_t* glyphas_block_get_items(
|
||||
const glyphas_block_t* block, size_t* len) {
|
||||
assert(block != NULL);
|
||||
assert(len != NULL);
|
||||
|
||||
*len = block->length;
|
||||
return block->items;
|
||||
}
|
102
util/glyphas/block.h
Normal file
102
util/glyphas/block.h
Normal file
@@ -0,0 +1,102 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "util/math/vector.h"
|
||||
#include "util/gleasy/atlas.h"
|
||||
|
||||
#include "./aligner.h"
|
||||
#include "./cache.h"
|
||||
|
||||
struct glyphas_block_t;
|
||||
typedef struct glyphas_block_t glyphas_block_t;
|
||||
|
||||
typedef struct {
|
||||
vec2_t pos;
|
||||
vec2_t size;
|
||||
vec4_t color;
|
||||
gleasy_atlas_geometry_t uv;
|
||||
} glyphas_block_item_t;
|
||||
|
||||
glyphas_block_t*
|
||||
glyphas_block_new(
|
||||
glyphas_aligner_direction_t dir,
|
||||
int32_t lineheight,
|
||||
int32_t maxpos,
|
||||
size_t reserve
|
||||
);
|
||||
|
||||
void
|
||||
glyphas_block_delete(
|
||||
glyphas_block_t* block
|
||||
);
|
||||
|
||||
void
|
||||
glyphas_block_clear(
|
||||
glyphas_block_t* block
|
||||
);
|
||||
|
||||
void
|
||||
glyphas_block_add_character(
|
||||
glyphas_block_t* block,
|
||||
const glyphas_cache_glyph_t* glyph,
|
||||
const vec4_t* color
|
||||
);
|
||||
|
||||
void
|
||||
glyphas_block_add_characters(
|
||||
glyphas_block_t* block,
|
||||
glyphas_cache_t* cache,
|
||||
const vec4_t* color,
|
||||
const char* str,
|
||||
size_t len
|
||||
);
|
||||
|
||||
void
|
||||
glyphas_block_break_line(
|
||||
glyphas_block_t* block
|
||||
);
|
||||
|
||||
void
|
||||
glyphas_block_scale(
|
||||
glyphas_block_t* block,
|
||||
const vec2_t* translation
|
||||
);
|
||||
|
||||
void
|
||||
glyphas_block_translate(
|
||||
glyphas_block_t* block,
|
||||
const vec2_t* translation
|
||||
);
|
||||
|
||||
void
|
||||
glyphas_block_set_origin(
|
||||
glyphas_block_t* block,
|
||||
const vec2_t* r
|
||||
);
|
||||
|
||||
void
|
||||
glyphas_block_set_alpha(
|
||||
glyphas_block_t* block,
|
||||
float a
|
||||
);
|
||||
|
||||
void
|
||||
glyphas_block_make_glitched(
|
||||
glyphas_block_t* block,
|
||||
uint64_t seed
|
||||
);
|
||||
|
||||
void
|
||||
glyphas_block_calculate_geometry(
|
||||
const glyphas_block_t* block,
|
||||
vec2_t* offset,
|
||||
vec2_t* size
|
||||
);
|
||||
|
||||
const glyphas_block_item_t*
|
||||
glyphas_block_get_items(
|
||||
const glyphas_block_t* block,
|
||||
size_t* len
|
||||
);
|
111
util/glyphas/cache.c
Normal file
111
util/glyphas/cache.c
Normal file
@@ -0,0 +1,111 @@
|
||||
#include "./cache.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <GL/glew.h>
|
||||
|
||||
#include "util/container/array.h"
|
||||
#include "util/gleasy/atlas.h"
|
||||
#include "util/memory/memory.h"
|
||||
|
||||
#include "./face.h"
|
||||
|
||||
struct glyphas_cache_t {
|
||||
gleasy_atlas_t* atlas;
|
||||
glyphas_face_t* face;
|
||||
|
||||
int32_t char_width;
|
||||
int32_t char_height;
|
||||
|
||||
/* TODO(catfoot): linear search isn't efficient. :( */
|
||||
CONTAINER_ARRAY glyphas_cache_glyph_t* items;
|
||||
};
|
||||
|
||||
glyphas_cache_t* glyphas_cache_new(
|
||||
gleasy_atlas_t* atlas,
|
||||
glyphas_face_t* face,
|
||||
int32_t char_width,
|
||||
int32_t char_height) {
|
||||
assert(atlas != NULL);
|
||||
assert(face != NULL);
|
||||
assert(char_width > 0);
|
||||
assert(char_height > 0);
|
||||
|
||||
glyphas_cache_t* cache = memory_new(sizeof(*cache));
|
||||
*cache = (typeof(*cache)) {
|
||||
.atlas = atlas,
|
||||
.face = face,
|
||||
.char_width = char_width,
|
||||
.char_height = char_height,
|
||||
};
|
||||
return cache;
|
||||
}
|
||||
|
||||
void glyphas_cache_delete(glyphas_cache_t* cache) {
|
||||
if (cache == NULL) return;
|
||||
|
||||
container_array_delete(cache->items);
|
||||
|
||||
memory_delete(cache);
|
||||
}
|
||||
|
||||
const glyphas_cache_glyph_t* glyphas_cache_add_glyph(
|
||||
glyphas_cache_t* cache, uint32_t unicode) {
|
||||
assert(cache != NULL);
|
||||
|
||||
const glyphas_cache_glyph_t* found =
|
||||
glyphas_cache_lookup_glyph(cache, unicode);
|
||||
if (found != NULL) return found;
|
||||
|
||||
if (!glyphas_face_set_pixel_size(
|
||||
cache->face, cache->char_width, cache->char_height)) {
|
||||
return NULL;
|
||||
}
|
||||
if (!glyphas_face_render_glyph(cache->face, unicode)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
gleasy_atlas_geometry_t geo = {0};
|
||||
|
||||
const glyphas_glyph_t* rendered = &cache->face->glyph;
|
||||
if (rendered->bitmap.width > 0) {
|
||||
const gleasy_atlas_bitmap_t bmp = {
|
||||
.width = rendered->bitmap.width,
|
||||
.height = rendered->bitmap.height,
|
||||
.buffer = rendered->bitmap.buffer,
|
||||
.format = GL_RED,
|
||||
.type = GL_UNSIGNED_BYTE,
|
||||
};
|
||||
if (!gleasy_atlas_add(cache->atlas, &geo, &bmp)) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
const size_t index = container_array_get_length(cache->items);
|
||||
container_array_insert(cache->items, index);
|
||||
|
||||
glyphas_cache_glyph_t* g = &cache->items[index];
|
||||
*g = (typeof(*g)) {
|
||||
.unicode = unicode,
|
||||
.width = rendered->bitmap.width,
|
||||
.height = rendered->bitmap.height,
|
||||
.geometry = geo,
|
||||
.hmetrics = rendered->hmetrics,
|
||||
.vmetrics = rendered->vmetrics,
|
||||
};
|
||||
return g;
|
||||
}
|
||||
|
||||
const glyphas_cache_glyph_t* glyphas_cache_lookup_glyph(
|
||||
const glyphas_cache_t* cache, uint32_t unicode) {
|
||||
assert(cache != NULL);
|
||||
|
||||
const size_t len = container_array_get_length(cache->items);
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
const glyphas_cache_glyph_t* g = &cache->items[i];
|
||||
if (g->unicode == unicode) return g;
|
||||
}
|
||||
return NULL;
|
||||
}
|
47
util/glyphas/cache.h
Normal file
47
util/glyphas/cache.h
Normal file
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "util/gleasy/atlas.h"
|
||||
|
||||
#include "./face.h"
|
||||
#include "./glyph.h"
|
||||
|
||||
typedef struct {
|
||||
uint32_t unicode;
|
||||
|
||||
int32_t width;
|
||||
int32_t height;
|
||||
|
||||
gleasy_atlas_geometry_t geometry;
|
||||
glyphas_glyph_metrics_t hmetrics;
|
||||
glyphas_glyph_metrics_t vmetrics;
|
||||
} glyphas_cache_glyph_t;
|
||||
|
||||
struct glyphas_cache_t;
|
||||
typedef struct glyphas_cache_t glyphas_cache_t;
|
||||
|
||||
glyphas_cache_t*
|
||||
glyphas_cache_new(
|
||||
gleasy_atlas_t* atlas,
|
||||
glyphas_face_t* face,
|
||||
int32_t char_width,
|
||||
int32_t char_height
|
||||
);
|
||||
|
||||
void
|
||||
glyphas_cache_delete(
|
||||
glyphas_cache_t* cache
|
||||
);
|
||||
|
||||
const glyphas_cache_glyph_t* /* NULLABLE */
|
||||
glyphas_cache_add_glyph(
|
||||
glyphas_cache_t* cache,
|
||||
uint32_t unicode
|
||||
);
|
||||
|
||||
const glyphas_cache_glyph_t* /* NULLABLE */
|
||||
glyphas_cache_lookup_glyph(
|
||||
const glyphas_cache_t* cache,
|
||||
uint32_t unicode
|
||||
);
|
21
util/glyphas/context.c
Normal file
21
util/glyphas/context.c
Normal file
@@ -0,0 +1,21 @@
|
||||
#include "./context.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include <ft2build.h>
|
||||
#include FT_FREETYPE_H
|
||||
|
||||
void glyphas_context_initialize(glyphas_context_t* ctx) {
|
||||
assert(ctx != NULL);
|
||||
|
||||
*ctx = (typeof(*ctx)) {0};
|
||||
|
||||
FT_Init_FreeType(&ctx->ft);
|
||||
}
|
||||
|
||||
void glyphas_context_deinitialize(glyphas_context_t* ctx) {
|
||||
assert(ctx != NULL);
|
||||
|
||||
FT_Done_FreeType(ctx->ft);
|
||||
}
|
18
util/glyphas/context.h
Normal file
18
util/glyphas/context.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include <ft2build.h>
|
||||
#include FT_FREETYPE_H
|
||||
|
||||
typedef struct {
|
||||
FT_Library ft;
|
||||
} glyphas_context_t;
|
||||
|
||||
void
|
||||
glyphas_context_initialize(
|
||||
glyphas_context_t* ctx
|
||||
);
|
||||
|
||||
void
|
||||
glyphas_context_deinitialize(
|
||||
glyphas_context_t* ctx
|
||||
);
|
164
util/glyphas/drawer.c
Normal file
164
util/glyphas/drawer.c
Normal file
@@ -0,0 +1,164 @@
|
||||
#include "./drawer.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include <GL/glew.h>
|
||||
|
||||
#include "util/gleasy/buffer.h"
|
||||
#include "util/gleasy/texture.h"
|
||||
#include "util/gleasy/program.h"
|
||||
#include "util/math/vector.h"
|
||||
#include "util/memory/memory.h"
|
||||
|
||||
#include "./block.h"
|
||||
|
||||
/* resources */
|
||||
#include "anysrc/drawer.vshader.h"
|
||||
#include "anysrc/drawer.fshader.h"
|
||||
|
||||
#define GLYPHAS_DRAWER_UNIFORM_TEX 0
|
||||
|
||||
#define GLYPHAS_DRAWER_VSHADER_IN_POS 0
|
||||
#define GLYPHAS_DRAWER_VSHADER_IN_SIZE 1
|
||||
#define GLYPHAS_DRAWER_VSHADER_IN_UV_POS 2
|
||||
#define GLYPHAS_DRAWER_VSHADER_IN_UV_SIZE 3
|
||||
#define GLYPHAS_DRAWER_VSHADER_IN_COLOR 4
|
||||
|
||||
struct glyphas_drawer_t {
|
||||
gleasy_texture_2d_t tex;
|
||||
|
||||
GLuint vao;
|
||||
|
||||
gleasy_buffer_array_t instances;
|
||||
size_t instances_reserved;
|
||||
size_t instances_length;
|
||||
};
|
||||
|
||||
#pragma pack(push, 1)
|
||||
typedef struct {
|
||||
vec2_t pos;
|
||||
vec2_t size;
|
||||
vec2_t uv_pos;
|
||||
vec2_t uv_size;
|
||||
vec4_t color;
|
||||
} glyphas_drawer_instance_t;
|
||||
#pragma pack(pop)
|
||||
|
||||
static void glyphas_drawer_setup_vao_(
|
||||
gleasy_buffer_array_t instances) {
|
||||
assert(instances != 0);
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, instances);
|
||||
|
||||
# define enable_attrib_(NAME, name, dim, type) do { \
|
||||
glEnableVertexAttribArray(GLYPHAS_DRAWER_VSHADER_IN_##NAME); \
|
||||
glVertexAttribPointer( \
|
||||
GLYPHAS_DRAWER_VSHADER_IN_##NAME, dim, type, GL_FALSE, \
|
||||
sizeof(glyphas_drawer_instance_t), \
|
||||
NULL + offsetof(glyphas_drawer_instance_t, name)); \
|
||||
glVertexAttribDivisor(GLYPHAS_DRAWER_VSHADER_IN_##NAME, 1); \
|
||||
} while (0)
|
||||
|
||||
enable_attrib_(POS, pos, 2, GL_FLOAT);
|
||||
enable_attrib_(SIZE, size, 2, GL_FLOAT);
|
||||
enable_attrib_(UV_POS, uv_pos, 2, GL_FLOAT);
|
||||
enable_attrib_(UV_SIZE, uv_size, 2, GL_FLOAT);
|
||||
enable_attrib_(COLOR, color, 4, GL_FLOAT);
|
||||
|
||||
# undef enable_attrib_
|
||||
}
|
||||
|
||||
gleasy_program_t glyphas_drawer_create_default_program(void) {
|
||||
return gleasy_program_new("", 0,
|
||||
glyphas_drawer_vshader_, sizeof(glyphas_drawer_vshader_),
|
||||
glyphas_drawer_fshader_, sizeof(glyphas_drawer_fshader_));
|
||||
}
|
||||
|
||||
glyphas_drawer_t* glyphas_drawer_new(void) {
|
||||
glyphas_drawer_t* drawer = memory_new(sizeof(*drawer));
|
||||
*drawer = (typeof(*drawer)) {0};
|
||||
|
||||
glCreateVertexArrays(1, &drawer->vao);
|
||||
glBindVertexArray(drawer->vao);
|
||||
|
||||
glGenBuffers(1, &drawer->instances);
|
||||
glyphas_drawer_setup_vao_(drawer->instances);
|
||||
|
||||
return drawer;
|
||||
}
|
||||
|
||||
void glyphas_drawer_delete(glyphas_drawer_t* drawer) {
|
||||
if (drawer == NULL) return;
|
||||
|
||||
glDeleteBuffers(1, &drawer->instances);
|
||||
|
||||
glDeleteVertexArrays(1, &drawer->vao);
|
||||
|
||||
memory_delete(drawer);
|
||||
}
|
||||
|
||||
void glyphas_drawer_clear(
|
||||
glyphas_drawer_t* drawer, gleasy_texture_2d_t tex, size_t reserve) {
|
||||
assert(drawer != NULL);
|
||||
assert(tex != 0);
|
||||
assert(reserve > 0);
|
||||
|
||||
drawer->tex = tex;
|
||||
|
||||
drawer->instances_length = 0;
|
||||
if (drawer->instances_reserved < reserve) {
|
||||
glBindBuffer(GL_ARRAY_BUFFER, drawer->instances);
|
||||
glBufferData(GL_ARRAY_BUFFER,
|
||||
reserve * sizeof(glyphas_drawer_instance_t),
|
||||
NULL, GL_DYNAMIC_DRAW);
|
||||
drawer->instances_reserved = reserve;
|
||||
}
|
||||
}
|
||||
|
||||
void glyphas_drawer_add_block(
|
||||
glyphas_drawer_t* drawer, const glyphas_block_t* block) {
|
||||
assert(drawer != NULL);
|
||||
assert(block != NULL);
|
||||
|
||||
size_t len;
|
||||
const glyphas_block_item_t* items = glyphas_block_get_items(block, &len);
|
||||
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
glyphas_drawer_add_block_item(drawer, &items[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void glyphas_drawer_add_block_item(
|
||||
glyphas_drawer_t* drawer, const glyphas_block_item_t* item) {
|
||||
assert(drawer != NULL);
|
||||
assert(item != NULL);
|
||||
|
||||
const glyphas_drawer_instance_t insta = {
|
||||
.pos = item->pos,
|
||||
.size = item->size,
|
||||
.uv_pos = vec2(item->uv.left, item->uv.top),
|
||||
.uv_size = vec2(item->uv.right-item->uv.left, item->uv.top-item->uv.bottom),
|
||||
.color = item->color,
|
||||
};
|
||||
|
||||
const size_t offset = drawer->instances_length * sizeof(insta);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, drawer->instances);
|
||||
glBufferSubData(GL_ARRAY_BUFFER, offset, sizeof(insta), &insta);
|
||||
|
||||
++drawer->instances_length;
|
||||
}
|
||||
|
||||
void glyphas_drawer_draw(const glyphas_drawer_t* drawer) {
|
||||
assert(drawer != NULL);
|
||||
|
||||
if (drawer->instances_length == 0) return;
|
||||
|
||||
glBindVertexArray(drawer->vao);
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, drawer->tex);
|
||||
glUniform1i(GLYPHAS_DRAWER_UNIFORM_TEX, 0);
|
||||
|
||||
glDrawArraysInstanced(GL_TRIANGLES, 0, 6, drawer->instances_length);
|
||||
}
|
14
util/glyphas/drawer.fshader
Normal file
14
util/glyphas/drawer.fshader
Normal file
@@ -0,0 +1,14 @@
|
||||
#version 330
|
||||
#extension GL_ARB_explicit_uniform_location : enable
|
||||
|
||||
layout (location = 0) uniform sampler2D u_tex;
|
||||
|
||||
in vec2 v_uv;
|
||||
in vec4 v_color;
|
||||
|
||||
out vec4 o_color;
|
||||
|
||||
void main(void) {
|
||||
float a = texture(u_tex, v_uv).r;
|
||||
o_color = vec4(v_color.rgb, v_color.a*a);
|
||||
}
|
51
util/glyphas/drawer.h
Normal file
51
util/glyphas/drawer.h
Normal file
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#include "util/gleasy/texture.h"
|
||||
#include "util/gleasy/program.h"
|
||||
|
||||
#include "./block.h"
|
||||
|
||||
struct glyphas_drawer_t;
|
||||
typedef struct glyphas_drawer_t glyphas_drawer_t;
|
||||
|
||||
gleasy_program_t
|
||||
glyphas_drawer_create_default_program(
|
||||
void
|
||||
);
|
||||
|
||||
glyphas_drawer_t*
|
||||
glyphas_drawer_new(
|
||||
void
|
||||
);
|
||||
|
||||
void
|
||||
glyphas_drawer_delete(
|
||||
glyphas_drawer_t* drawer
|
||||
);
|
||||
|
||||
void
|
||||
glyphas_drawer_clear(
|
||||
glyphas_drawer_t* drawer,
|
||||
gleasy_texture_2d_t tex,
|
||||
size_t reserve
|
||||
);
|
||||
|
||||
void
|
||||
glyphas_drawer_add_block(
|
||||
glyphas_drawer_t* drawer,
|
||||
const glyphas_block_t* block
|
||||
/* The drawer doesn't hold the reference. */
|
||||
);
|
||||
|
||||
void
|
||||
glyphas_drawer_add_block_item(
|
||||
glyphas_drawer_t* drawer,
|
||||
const glyphas_block_item_t* item
|
||||
/* The drawer doesn't hold the reference. */
|
||||
);
|
||||
|
||||
/* bind the program before drawing */
|
||||
void
|
||||
glyphas_drawer_draw(
|
||||
const glyphas_drawer_t* drawer
|
||||
);
|
25
util/glyphas/drawer.vshader
Normal file
25
util/glyphas/drawer.vshader
Normal file
@@ -0,0 +1,25 @@
|
||||
#version 330
|
||||
|
||||
layout (location = 0) in vec2 i_pos;
|
||||
layout (location = 1) in vec2 i_size;
|
||||
layout (location = 2) in vec2 i_uv_pos;
|
||||
layout (location = 3) in vec2 i_uv_size;
|
||||
layout (location = 4) in vec4 i_color;
|
||||
|
||||
out vec2 v_uv;
|
||||
out vec4 v_color;
|
||||
|
||||
void main(void) {
|
||||
vec2 p =
|
||||
gl_VertexID == 0? vec2(0., 0.):
|
||||
gl_VertexID == 1? vec2(0., -1.):
|
||||
gl_VertexID == 2? vec2(1., -1.):
|
||||
gl_VertexID == 3? vec2(0., 0.):
|
||||
gl_VertexID == 4? vec2(1., -1.):
|
||||
gl_VertexID == 5? vec2(1., 0.):
|
||||
vec2(0.);
|
||||
|
||||
gl_Position = vec4(p*i_size+i_pos, 0, 1);
|
||||
v_uv = p*i_uv_size + i_uv_pos;
|
||||
v_color = i_color;
|
||||
}
|
78
util/glyphas/face.c
Normal file
78
util/glyphas/face.c
Normal file
@@ -0,0 +1,78 @@
|
||||
#include "./face.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <ft2build.h>
|
||||
#include FT_ERRORS_H
|
||||
#include FT_FREETYPE_H
|
||||
|
||||
#include "./context.h"
|
||||
|
||||
void glyphas_face_initialize_from_file(
|
||||
glyphas_face_t* face,
|
||||
const glyphas_context_t* ctx,
|
||||
const char* path,
|
||||
size_t index) {
|
||||
assert(face != NULL);
|
||||
assert(ctx != NULL);
|
||||
|
||||
*face = (typeof(*face)) {0};
|
||||
|
||||
const FT_Error err = FT_New_Face(ctx->ft, path, index, &face->ft);
|
||||
if (err != FT_Err_Ok) {
|
||||
fprintf(stderr,
|
||||
"failed to load font file '%s': %s\n", path, FT_Error_String(err));
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
void glyphas_face_initialize_from_buffer(
|
||||
glyphas_face_t* face,
|
||||
const glyphas_context_t* ctx,
|
||||
const void* data,
|
||||
size_t length,
|
||||
size_t index) {
|
||||
assert(face != NULL);
|
||||
assert(ctx != NULL);
|
||||
|
||||
*face = (typeof(*face)) {0};
|
||||
|
||||
const FT_Error err =
|
||||
FT_New_Memory_Face(ctx->ft, data, length, index, &face->ft);
|
||||
if (err != FT_Err_Ok) {
|
||||
fprintf(stderr,
|
||||
"failed to load font on memory: %s\n", FT_Error_String(err));
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
void glyphas_face_deinitialize(glyphas_face_t* face) {
|
||||
assert(face != NULL);
|
||||
|
||||
FT_Done_Face(face->ft);
|
||||
}
|
||||
|
||||
bool glyphas_face_set_pixel_size(
|
||||
glyphas_face_t* face, int32_t width, int32_t height) {
|
||||
assert(face != NULL);
|
||||
assert(width > 0);
|
||||
assert(height > 0);
|
||||
|
||||
const FT_Error err = FT_Set_Pixel_Sizes(face->ft, width, height);
|
||||
return err == FT_Err_Ok;
|
||||
}
|
||||
|
||||
bool glyphas_face_render_glyph(glyphas_face_t* face, uint64_t unicode) {
|
||||
assert(face != NULL);
|
||||
|
||||
const FT_UInt index = FT_Get_Char_Index(face->ft, unicode);
|
||||
if (index == 0) return false;
|
||||
|
||||
const FT_Error err = FT_Load_Glyph(face->ft, index, FT_LOAD_RENDER);
|
||||
if (err != FT_Err_Ok) return false;
|
||||
|
||||
return glyphas_glyph_reset_from_ft_glyph_slot(&face->glyph, face->ft->glyph);
|
||||
}
|
52
util/glyphas/face.h
Normal file
52
util/glyphas/face.h
Normal file
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include <ft2build.h>
|
||||
#include FT_FREETYPE_H
|
||||
|
||||
#include "./context.h"
|
||||
#include "./glyph.h"
|
||||
|
||||
typedef struct {
|
||||
FT_Face ft;
|
||||
|
||||
glyphas_glyph_t glyph;
|
||||
} glyphas_face_t;
|
||||
|
||||
void
|
||||
glyphas_face_initialize_from_file(
|
||||
glyphas_face_t* face,
|
||||
const glyphas_context_t* ctx,
|
||||
const char* path,
|
||||
size_t index
|
||||
);
|
||||
|
||||
void
|
||||
glyphas_face_initialize_from_buffer(
|
||||
glyphas_face_t* face,
|
||||
const glyphas_context_t* ctx,
|
||||
const void* data,
|
||||
size_t length,
|
||||
size_t index
|
||||
);
|
||||
|
||||
void
|
||||
glyphas_face_deinitialize(
|
||||
glyphas_face_t* face
|
||||
);
|
||||
|
||||
bool
|
||||
glyphas_face_set_pixel_size(
|
||||
glyphas_face_t* face,
|
||||
int32_t width,
|
||||
int32_t height
|
||||
);
|
||||
|
||||
/* Set sizes before rendering glyphs. */
|
||||
bool
|
||||
glyphas_face_render_glyph(
|
||||
glyphas_face_t* face,
|
||||
uint64_t unicode
|
||||
);
|
36
util/glyphas/glyph.c
Normal file
36
util/glyphas/glyph.c
Normal file
@@ -0,0 +1,36 @@
|
||||
#include "./glyph.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <ft2build.h>
|
||||
#include FT_FREETYPE_H
|
||||
|
||||
bool glyphas_glyph_reset_from_ft_glyph_slot(
|
||||
glyphas_glyph_t* glyph, FT_GlyphSlot slot) {
|
||||
assert(glyph != NULL);
|
||||
assert(slot != NULL);
|
||||
|
||||
if (slot->bitmap.pixel_mode != FT_PIXEL_MODE_GRAY) return false;
|
||||
|
||||
*glyph = (typeof(*glyph)) {
|
||||
.bitmap = {
|
||||
.width = slot->bitmap.width,
|
||||
.height = slot->bitmap.rows,
|
||||
.buffer = slot->bitmap.buffer,
|
||||
},
|
||||
.hmetrics = {
|
||||
.bear_x = slot->metrics.horiBearingX/64,
|
||||
.bear_y = slot->metrics.horiBearingY/64,
|
||||
.advance = slot->metrics.horiAdvance /64,
|
||||
},
|
||||
.vmetrics = {
|
||||
.bear_x = slot->metrics.vertBearingX/64,
|
||||
.bear_y = slot->metrics.vertBearingY/64,
|
||||
.advance = slot->metrics.vertAdvance /64,
|
||||
},
|
||||
};
|
||||
return true;
|
||||
}
|
32
util/glyphas/glyph.h
Normal file
32
util/glyphas/glyph.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <ft2build.h>
|
||||
#include FT_FREETYPE_H
|
||||
|
||||
typedef struct {
|
||||
int32_t width;
|
||||
int32_t height;
|
||||
uint8_t* buffer; /* gray-scale bitmap */
|
||||
} glyphas_glyph_bitmap_t;
|
||||
|
||||
typedef struct {
|
||||
int32_t bear_x;
|
||||
int32_t bear_y;
|
||||
int32_t advance;
|
||||
} glyphas_glyph_metrics_t;
|
||||
|
||||
typedef struct {
|
||||
glyphas_glyph_bitmap_t bitmap;
|
||||
glyphas_glyph_metrics_t vmetrics;
|
||||
glyphas_glyph_metrics_t hmetrics;
|
||||
} glyphas_glyph_t;
|
||||
|
||||
/* The glyph's lifetime must be shorter than the slot. */
|
||||
bool
|
||||
glyphas_glyph_reset_from_ft_glyph_slot(
|
||||
glyphas_glyph_t* glyph,
|
||||
FT_GlyphSlot slot
|
||||
);
|
179
util/glyphas/test.c
Normal file
179
util/glyphas/test.c
Normal file
@@ -0,0 +1,179 @@
|
||||
/* An expression in assert() can be expected to be executed absolutely. */
|
||||
#undef NDEBUG
|
||||
|
||||
#define SDL_MAIN_HANDLED
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <GL/glew.h>
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
#include "util/conv/charcode.h"
|
||||
#include "util/gleasy/buffer.h"
|
||||
#include "util/gleasy/program.h"
|
||||
#include "util/math/vector.h"
|
||||
|
||||
#include "./cache.h"
|
||||
#include "./context.h"
|
||||
#include "./drawer.h"
|
||||
#include "./face.h"
|
||||
#include "./glyph.h"
|
||||
|
||||
typedef struct {
|
||||
glyphas_context_t glyphas;
|
||||
glyphas_face_t face;
|
||||
|
||||
gleasy_atlas_t* atlas;
|
||||
glyphas_cache_t* cache;
|
||||
|
||||
gleasy_program_t prog;
|
||||
glyphas_drawer_t* drawer;
|
||||
} context_t;
|
||||
|
||||
#define SCALE 0.01f
|
||||
|
||||
static void align_text_(context_t* ctx, const char* str) {
|
||||
assert(ctx != NULL);
|
||||
|
||||
const size_t len = strlen(str);
|
||||
glyphas_block_t* block = glyphas_block_new(
|
||||
GLYPHAS_ALIGNER_DIRECTION_HORIZONTAL, -20, 400, len);
|
||||
|
||||
static vec4_t color = vec4(0, 0, 0, 1);
|
||||
glyphas_block_add_characters(block, ctx->cache, &color, str, len);
|
||||
|
||||
vec2_t offset, size;
|
||||
glyphas_block_calculate_geometry(block, &offset, &size);
|
||||
|
||||
offset = vec2(-offset.x, 0);
|
||||
glyphas_block_translate(block, &offset);
|
||||
|
||||
size = vec2(1.f/200, 1.f/200);
|
||||
glyphas_block_scale(block, &size);
|
||||
|
||||
static const vec2_t origin = vec2(.5f, -.5f);
|
||||
glyphas_block_set_origin(block, &origin);
|
||||
|
||||
glyphas_drawer_clear(ctx->drawer, gleasy_atlas_get_texture(ctx->atlas), len);
|
||||
glyphas_drawer_add_block(ctx->drawer, block);
|
||||
glyphas_block_delete(block);
|
||||
}
|
||||
|
||||
static void initialize_(context_t* ctx, const char* path, const char* str) {
|
||||
assert(ctx != NULL);
|
||||
|
||||
*ctx = (typeof(*ctx)) {0};
|
||||
glyphas_context_initialize(&ctx->glyphas);
|
||||
|
||||
glyphas_face_initialize_from_file(&ctx->face, &ctx->glyphas, path, 0);
|
||||
|
||||
ctx->atlas = gleasy_atlas_new(GL_RED, 256, 256, false /* anti-alias */);
|
||||
ctx->cache = glyphas_cache_new(ctx->atlas, &ctx->face, 16, 16);
|
||||
|
||||
ctx->prog = glyphas_drawer_create_default_program();
|
||||
ctx->drawer = glyphas_drawer_new();
|
||||
assert(glGetError() == GL_NO_ERROR);
|
||||
|
||||
align_text_(ctx, str);
|
||||
assert(glGetError() == GL_NO_ERROR);
|
||||
}
|
||||
|
||||
static void draw_(const context_t* ctx) {
|
||||
assert(ctx != NULL);
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
glUseProgram(ctx->prog);
|
||||
glyphas_drawer_draw(ctx->drawer);
|
||||
}
|
||||
|
||||
static void deinitialize_(context_t* ctx) {
|
||||
assert(ctx != NULL);
|
||||
|
||||
glyphas_drawer_delete(ctx->drawer);
|
||||
glDeleteProgram(ctx->prog);
|
||||
|
||||
glyphas_cache_delete(ctx->cache);
|
||||
gleasy_atlas_delete(ctx->atlas);
|
||||
|
||||
glyphas_face_deinitialize(&ctx->face);
|
||||
glyphas_context_deinitialize(&ctx->glyphas);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
SDL_Window* win;
|
||||
SDL_GLContext gl;
|
||||
} libs_t;
|
||||
|
||||
static void initialize_libraries_(libs_t* libs) {
|
||||
assert(libs != NULL);
|
||||
|
||||
SDL_SetHint(SDL_HINT_VIDEO_HIGHDPI_DISABLED, "0");
|
||||
assert(SDL_Init(SDL_INIT_VIDEO) == 0);
|
||||
|
||||
SDL_GL_SetAttribute(
|
||||
SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG);
|
||||
SDL_GL_SetAttribute(
|
||||
SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
||||
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
|
||||
|
||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
||||
|
||||
static const uint32_t win_flags =
|
||||
SDL_WINDOW_ALLOW_HIGHDPI |
|
||||
SDL_WINDOW_OPENGL |
|
||||
SDL_WINDOW_SHOWN;
|
||||
libs->win = SDL_CreateWindow(
|
||||
"glyphas-test", /* title */
|
||||
SDL_WINDOWPOS_CENTERED, /* X position */
|
||||
SDL_WINDOWPOS_CENTERED, /* Y position */
|
||||
400,
|
||||
400,
|
||||
win_flags);
|
||||
assert(libs->win != NULL);
|
||||
|
||||
libs->gl = SDL_GL_CreateContext(libs->win);
|
||||
|
||||
glewExperimental = 1;
|
||||
assert(glewInit() == GLEW_OK);
|
||||
}
|
||||
static void deinitialize_libraries_(libs_t* libs) {
|
||||
assert(libs != NULL);
|
||||
|
||||
SDL_GL_DeleteContext(libs->gl);
|
||||
SDL_DestroyWindow(libs->win);
|
||||
SDL_Quit();
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
assert(argc == 3);
|
||||
|
||||
libs_t libs;
|
||||
initialize_libraries_(&libs);
|
||||
|
||||
context_t ctx;
|
||||
initialize_(&ctx, argv[1], argv[2]);
|
||||
|
||||
bool alive = true;
|
||||
while (alive) {
|
||||
SDL_Delay(30);
|
||||
|
||||
SDL_Event e;
|
||||
while (SDL_PollEvent(&e)) {
|
||||
if (e.type == SDL_QUIT) alive = false;
|
||||
}
|
||||
glClearColor(1, 1, 1, 1);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
draw_(&ctx);
|
||||
SDL_GL_SwapWindow(libs.win);
|
||||
}
|
||||
|
||||
deinitialize_(&ctx);
|
||||
deinitialize_libraries_(&libs);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
23
util/jukebox/CMakeLists.txt
Normal file
23
util/jukebox/CMakeLists.txt
Normal file
@@ -0,0 +1,23 @@
|
||||
add_library(jukebox
|
||||
amp.c
|
||||
beep.c
|
||||
composite.c
|
||||
decoder.c
|
||||
delay.c
|
||||
effect.c
|
||||
format.c
|
||||
lowpass.c
|
||||
mixer.c
|
||||
sound.c
|
||||
)
|
||||
target_link_libraries(jukebox
|
||||
miniaudio
|
||||
|
||||
math
|
||||
memory
|
||||
)
|
||||
|
||||
if (BUILD_TESTING)
|
||||
add_executable(jukebox-test test.c)
|
||||
target_link_libraries(jukebox-test jukebox)
|
||||
endif()
|
83
util/jukebox/amp.c
Normal file
83
util/jukebox/amp.c
Normal file
@@ -0,0 +1,83 @@
|
||||
#include "./amp.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "util/math/algorithm.h"
|
||||
#include "util/math/rational.h"
|
||||
|
||||
#include "./effect.h"
|
||||
#include "./format.h"
|
||||
|
||||
/* FIXME: Whether this solution works fine,
|
||||
depends on how long the operation is done. */
|
||||
|
||||
static void jukebox_amp_affect_(
|
||||
jukebox_effect_t* effect, const jukebox_effect_pcm_t* pcm) {
|
||||
assert(effect != NULL);
|
||||
assert(pcm != NULL);
|
||||
|
||||
jukebox_amp_t* amp = (typeof(amp)) effect;
|
||||
|
||||
const uint64_t easedur = atomic_load(&->ease_duration);
|
||||
const uint64_t elapsed = atomic_load(&->elapsed);
|
||||
|
||||
const float prev = atomic_load(&->prev_amount)/1000.0f;
|
||||
const float next = atomic_load(&->next_amount)/1000.0f;
|
||||
|
||||
float* ptr = pcm->ptr;
|
||||
for (int32_t i = 0; i < pcm->frames; ++i) {
|
||||
float a = next;
|
||||
if (elapsed < easedur) {
|
||||
a = (prev-next) * (easedur-elapsed) / easedur + next;
|
||||
}
|
||||
|
||||
for (int32_t j = 0; j < amp->format.channels; ++j) {
|
||||
*(ptr++) *= a;
|
||||
}
|
||||
}
|
||||
atomic_fetch_add(&->elapsed, pcm->frames);
|
||||
}
|
||||
|
||||
void jukebox_amp_initialize(
|
||||
jukebox_amp_t* amp, const jukebox_format_t* format) {
|
||||
assert(amp != NULL);
|
||||
assert(jukebox_format_valid(format));
|
||||
|
||||
*amp = (typeof(*amp)) {
|
||||
.super = {
|
||||
.vtable = {
|
||||
.affect = jukebox_amp_affect_,
|
||||
},
|
||||
},
|
||||
.format = *format,
|
||||
};
|
||||
}
|
||||
|
||||
void jukebox_amp_deinitialize(jukebox_amp_t* amp) {
|
||||
assert(amp != NULL);
|
||||
|
||||
}
|
||||
|
||||
void jukebox_amp_change_volume(
|
||||
jukebox_amp_t* amp, float amount, const rational_t* duration) {
|
||||
assert(amp != NULL);
|
||||
assert(MATH_FLOAT_VALID(amount));
|
||||
assert(amount >= 0);
|
||||
|
||||
const float srate = amp->format.sample_rate;
|
||||
|
||||
atomic_store(&->ease_duration, 0);
|
||||
|
||||
atomic_store(&->elapsed, 0);
|
||||
atomic_store(&->prev_amount, amp->next_amount);
|
||||
|
||||
rational_t dur = rational(1, 100);
|
||||
if (rational_valid(duration)) rational_addeq(&dur, duration);
|
||||
rational_normalize(&dur, srate);
|
||||
|
||||
atomic_store(&->next_amount, (uint16_t) (amount*1000));
|
||||
atomic_store(&->ease_duration, dur.num);
|
||||
}
|
38
util/jukebox/amp.h
Normal file
38
util/jukebox/amp.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdatomic.h>
|
||||
|
||||
#include "util/math/rational.h"
|
||||
|
||||
#include "./effect.h"
|
||||
#include "./format.h"
|
||||
|
||||
typedef struct {
|
||||
jukebox_effect_t super;
|
||||
|
||||
jukebox_format_t format;
|
||||
|
||||
atomic_uint_least64_t elapsed;
|
||||
atomic_uint_least64_t ease_duration;
|
||||
|
||||
atomic_uint_least16_t prev_amount; /* [1/1000] */
|
||||
atomic_uint_least16_t next_amount; /* [1/1000] */
|
||||
} jukebox_amp_t;
|
||||
|
||||
void
|
||||
jukebox_amp_initialize(
|
||||
jukebox_amp_t* amp,
|
||||
const jukebox_format_t* format
|
||||
);
|
||||
|
||||
void
|
||||
jukebox_amp_deinitialize(
|
||||
jukebox_amp_t* amp
|
||||
);
|
||||
|
||||
void
|
||||
jukebox_amp_change_volume(
|
||||
jukebox_amp_t* amp,
|
||||
float amount,
|
||||
const rational_t* duration /* NULLABLE */
|
||||
);
|
77
util/jukebox/beep.c
Normal file
77
util/jukebox/beep.c
Normal file
@@ -0,0 +1,77 @@
|
||||
#include "./beep.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include "util/math/algorithm.h"
|
||||
#include "util/math/constant.h"
|
||||
|
||||
#include "./effect.h"
|
||||
#include "./format.h"
|
||||
|
||||
static void jukebox_beep_affect_(
|
||||
jukebox_effect_t* effect, const jukebox_effect_pcm_t* pcm) {
|
||||
assert(effect != NULL);
|
||||
assert(pcm != NULL);
|
||||
|
||||
jukebox_beep_t* b = (typeof(b)) effect;
|
||||
if (!atomic_load(&b->playing)) return;
|
||||
|
||||
const int32_t sample_rate = b->format.sample_rate;
|
||||
const int32_t channels = b->format.channels;
|
||||
const float omega = 2 * MATH_PI * b->hz;
|
||||
|
||||
float* ptr = pcm->ptr;
|
||||
for (int32_t i = 0; i < pcm->frames; ++i) {
|
||||
const float a = fmod(i*omega/sample_rate, 2*MATH_PI);
|
||||
const float x = sin(a + b->phase) * b->amp;
|
||||
for (int32_t j = 0; j < channels; ++j) {
|
||||
*(ptr++) += x;
|
||||
}
|
||||
}
|
||||
|
||||
b->phase += pcm->frames*1.0f/sample_rate * omega;
|
||||
b->phase = fmod(b->phase, 2*MATH_PI);
|
||||
}
|
||||
|
||||
void jukebox_beep_initialize(jukebox_beep_t* beep, const jukebox_format_t* format) {
|
||||
assert(beep != NULL);
|
||||
assert(jukebox_format_valid(format));
|
||||
|
||||
*beep = (typeof(*beep)) {
|
||||
.super = {
|
||||
.vtable = {
|
||||
.affect = jukebox_beep_affect_,
|
||||
},
|
||||
},
|
||||
.format = *format,
|
||||
};
|
||||
}
|
||||
|
||||
void jukebox_beep_deinitialize(jukebox_beep_t* beep) {
|
||||
assert(beep != NULL);
|
||||
|
||||
}
|
||||
|
||||
void jukebox_beep_play(jukebox_beep_t* beep, float hz, float amp) {
|
||||
assert(beep != NULL);
|
||||
assert(MATH_FLOAT_VALID(hz) && hz > 0);
|
||||
assert(MATH_FLOAT_VALID(amp) && amp > 0);
|
||||
|
||||
if (beep->playing) return;
|
||||
|
||||
beep->hz = hz;
|
||||
beep->amp = amp;
|
||||
beep->phase = 0;
|
||||
atomic_store(&beep->playing, true);
|
||||
}
|
||||
|
||||
void jukebox_beep_stop(jukebox_beep_t* beep) {
|
||||
assert(beep != NULL);
|
||||
|
||||
if (!beep->playing) return;
|
||||
atomic_store(&beep->playing, false);
|
||||
}
|
42
util/jukebox/beep.h
Normal file
42
util/jukebox/beep.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdatomic.h>
|
||||
|
||||
#include "./effect.h"
|
||||
#include "./format.h"
|
||||
|
||||
typedef struct {
|
||||
jukebox_effect_t super;
|
||||
jukebox_format_t format;
|
||||
|
||||
atomic_bool playing;
|
||||
|
||||
float hz;
|
||||
float amp;
|
||||
float phase;
|
||||
} jukebox_beep_t;
|
||||
|
||||
void
|
||||
jukebox_beep_initialize(
|
||||
jukebox_beep_t* beep,
|
||||
const jukebox_format_t* format
|
||||
);
|
||||
|
||||
void
|
||||
jukebox_beep_deinitialize(
|
||||
jukebox_beep_t* beep
|
||||
);
|
||||
|
||||
/* not thread-safe function */
|
||||
void
|
||||
jukebox_beep_play(
|
||||
jukebox_beep_t* beep,
|
||||
float hz,
|
||||
float amp
|
||||
);
|
||||
|
||||
/* not thread-safe function */
|
||||
void
|
||||
jukebox_beep_stop(
|
||||
jukebox_beep_t* beep
|
||||
);
|
113
util/jukebox/composite.c
Normal file
113
util/jukebox/composite.c
Normal file
@@ -0,0 +1,113 @@
|
||||
#include "./composite.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "util/math/algorithm.h"
|
||||
#include "util/memory/memory.h"
|
||||
|
||||
#include "./effect.h"
|
||||
#include "./format.h"
|
||||
|
||||
struct jukebox_composite_t {
|
||||
jukebox_effect_t super;
|
||||
|
||||
jukebox_format_t format;
|
||||
|
||||
atomic_bool playing;
|
||||
|
||||
size_t effects_reserved;
|
||||
size_t effects_length;
|
||||
jukebox_effect_t* effects[1];
|
||||
};
|
||||
|
||||
static void jukebox_composite_affect_(
|
||||
jukebox_effect_t* effect, const jukebox_effect_pcm_t* pcm) {
|
||||
assert(effect != NULL);
|
||||
assert(pcm != NULL);
|
||||
|
||||
# define CHUNK_SIZE 256
|
||||
|
||||
jukebox_composite_t* c = (typeof(c)) effect;
|
||||
if (!atomic_load(&c->playing)) return;
|
||||
|
||||
const int32_t channels = c->format.channels;
|
||||
const size_t chunk_frames = CHUNK_SIZE / channels;
|
||||
|
||||
uint64_t read = 0;
|
||||
while (read < (uint64_t) pcm->frames) {
|
||||
float chunk[CHUNK_SIZE] = {0};
|
||||
|
||||
jukebox_effect_pcm_t chunk_pcm = {
|
||||
.ptr = chunk,
|
||||
.frames = MATH_MIN(chunk_frames, pcm->frames - read),
|
||||
};
|
||||
for (size_t i = 0; i < c->effects_length; ++i) {
|
||||
jukebox_effect_affect(c->effects[i], &chunk_pcm);
|
||||
}
|
||||
|
||||
const size_t len = chunk_pcm.frames * channels;
|
||||
const float* src = chunk;
|
||||
float* dst = pcm->ptr + read * channels;
|
||||
for (uint64_t i = 0; i < len; ++i) {
|
||||
*(dst++) += *(src++);
|
||||
}
|
||||
read += chunk_pcm.frames;
|
||||
}
|
||||
|
||||
# undef CHUNK_SIZE
|
||||
}
|
||||
|
||||
jukebox_composite_t* jukebox_composite_new(
|
||||
const jukebox_format_t* format, size_t reserve) {
|
||||
assert(jukebox_format_valid(format));
|
||||
assert(reserve > 0);
|
||||
|
||||
jukebox_composite_t* compo =
|
||||
memory_new(sizeof(*compo) + (reserve-1)*sizeof(compo->effects[0]));
|
||||
*compo = (typeof(*compo)) {
|
||||
.super = {
|
||||
.vtable = {
|
||||
.affect = jukebox_composite_affect_,
|
||||
},
|
||||
},
|
||||
.format = *format,
|
||||
.effects_reserved = reserve,
|
||||
};
|
||||
return compo;
|
||||
}
|
||||
|
||||
void jukebox_composite_delete(jukebox_composite_t* compo) {
|
||||
if (compo == NULL) return;
|
||||
|
||||
memory_delete(compo);
|
||||
}
|
||||
|
||||
void jukebox_composite_add_effect(
|
||||
jukebox_composite_t* compo, jukebox_effect_t* effect) {
|
||||
assert(compo != NULL);
|
||||
assert(effect != NULL);
|
||||
|
||||
assert(!atomic_load(&compo->playing));
|
||||
|
||||
if (compo->effects_length >= compo->effects_reserved) {
|
||||
fprintf(stderr, "jukebox: composite effect overflow\n");
|
||||
abort();
|
||||
}
|
||||
compo->effects[compo->effects_length++] = effect;
|
||||
}
|
||||
|
||||
void jukebox_composite_play(jukebox_composite_t* compo) {
|
||||
assert(compo != NULL);
|
||||
|
||||
atomic_store(&compo->playing, true);
|
||||
}
|
||||
|
||||
void jukebox_composite_stop(jukebox_composite_t* compo) {
|
||||
assert(compo != NULL);
|
||||
|
||||
atomic_store(&compo->playing, false);
|
||||
}
|
36
util/jukebox/composite.h
Normal file
36
util/jukebox/composite.h
Normal file
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "./effect.h"
|
||||
#include "./format.h"
|
||||
|
||||
struct jukebox_composite_t;
|
||||
typedef struct jukebox_composite_t jukebox_composite_t;
|
||||
|
||||
jukebox_composite_t* /* OWNERSHIP */
|
||||
jukebox_composite_new(
|
||||
const jukebox_format_t* format,
|
||||
size_t reserve
|
||||
);
|
||||
|
||||
void
|
||||
jukebox_composite_delete(
|
||||
jukebox_composite_t* compo /* OWNERSHIP */
|
||||
);
|
||||
|
||||
void
|
||||
jukebox_composite_add_effect(
|
||||
jukebox_composite_t* compo, /* must be stopped */
|
||||
jukebox_effect_t* effect /* must be alive while the comp is alive */
|
||||
);
|
||||
|
||||
void
|
||||
jukebox_composite_play(
|
||||
jukebox_composite_t* compo
|
||||
);
|
||||
|
||||
void
|
||||
jukebox_composite_stop(
|
||||
jukebox_composite_t* compo
|
||||
);
|
217
util/jukebox/decoder.c
Normal file
217
util/jukebox/decoder.c
Normal file
@@ -0,0 +1,217 @@
|
||||
#include "./decoder.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "thirdparty/miniaudio/miniaudio.h"
|
||||
|
||||
#include "util/math/algorithm.h"
|
||||
#include "util/math/rational.h"
|
||||
#include "util/memory/memory.h"
|
||||
|
||||
#include "./effect.h"
|
||||
#include "./format.h"
|
||||
|
||||
# define JUKEBOX_DECODER_AFFECTION_CHUNK_SIZE 256
|
||||
|
||||
struct jukebox_decoder_t {
|
||||
jukebox_effect_t super;
|
||||
|
||||
jukebox_format_t format;
|
||||
|
||||
ma_decoder ma;
|
||||
|
||||
atomic_bool playing;
|
||||
atomic_int_least64_t duration; /* negative = 0 */
|
||||
|
||||
atomic_uint_least64_t current; /* main thread doesn't modify when playing*/
|
||||
bool loop;
|
||||
bool request_seek;
|
||||
};
|
||||
|
||||
static void jukebox_decoder_seek_(jukebox_decoder_t* dec, uint64_t f) {
|
||||
assert(dec != NULL);
|
||||
assert(atomic_load(&dec->playing));
|
||||
|
||||
if (ma_decoder_seek_to_pcm_frame(&dec->ma, f) != MA_SUCCESS) {
|
||||
fprintf(stderr, "jukebox: failed to seek ma_decoder\n");
|
||||
abort();
|
||||
}
|
||||
atomic_store(&dec->current, f);
|
||||
dec->request_seek = false;
|
||||
}
|
||||
|
||||
static void jukebox_decoder_affect_(
|
||||
jukebox_effect_t* effect, const jukebox_effect_pcm_t* pcm) {
|
||||
assert(effect != NULL);
|
||||
assert(pcm != NULL);
|
||||
|
||||
jukebox_decoder_t* dec = (typeof(dec)) effect;
|
||||
if (!atomic_load(&dec->playing)) return;
|
||||
|
||||
if (dec->request_seek) {
|
||||
jukebox_decoder_seek_(dec, atomic_load(&dec->current));
|
||||
}
|
||||
|
||||
const int32_t ch = dec->format.channels;
|
||||
|
||||
uint64_t read_frames = 0;
|
||||
for (;;) {
|
||||
const int64_t duration = atomic_load(&dec->duration);
|
||||
const uint64_t requested_frames = pcm->frames - read_frames;
|
||||
|
||||
if (duration <= 0) {
|
||||
atomic_store(&dec->playing, false);
|
||||
break;
|
||||
}
|
||||
if (read_frames >= (uint64_t) pcm->frames) break;
|
||||
|
||||
uint64_t chunk_frames = JUKEBOX_DECODER_AFFECTION_CHUNK_SIZE / ch;
|
||||
if (chunk_frames > (uint64_t) duration) chunk_frames = duration;
|
||||
if (chunk_frames > requested_frames) chunk_frames = requested_frames;
|
||||
|
||||
float chunk[JUKEBOX_DECODER_AFFECTION_CHUNK_SIZE];
|
||||
|
||||
const uint64_t decoded_frames =
|
||||
ma_decoder_read_pcm_frames(&dec->ma, chunk, chunk_frames);
|
||||
if (decoded_frames != chunk_frames) {
|
||||
jukebox_decoder_seek_(dec, 0);
|
||||
}
|
||||
|
||||
assert(read_frames+decoded_frames <= (uint64_t) pcm->frames);
|
||||
for (size_t i = 0; i < decoded_frames*ch; ++i) {
|
||||
pcm->ptr[read_frames*ch+i] += chunk[i];
|
||||
}
|
||||
read_frames += decoded_frames;
|
||||
|
||||
atomic_fetch_add(&dec->duration, -decoded_frames);
|
||||
atomic_fetch_add(&dec->current, decoded_frames);
|
||||
}
|
||||
}
|
||||
|
||||
static jukebox_decoder_t* jukebox_decoder_new_(
|
||||
const jukebox_format_t* format) {
|
||||
assert(jukebox_format_valid(format));
|
||||
|
||||
jukebox_decoder_t* decoder = memory_new(sizeof(*decoder));
|
||||
*decoder = (typeof(*decoder)) {
|
||||
.super = {
|
||||
.vtable = {
|
||||
.affect = jukebox_decoder_affect_,
|
||||
},
|
||||
},
|
||||
.format = *format,
|
||||
};
|
||||
return decoder;
|
||||
}
|
||||
|
||||
jukebox_decoder_t* jukebox_decoder_new_from_file(
|
||||
const jukebox_format_t* format, const char* path) {
|
||||
assert(jukebox_format_valid(format));
|
||||
|
||||
jukebox_decoder_t* decoder = jukebox_decoder_new_(format);
|
||||
|
||||
const ma_decoder_config cfg = ma_decoder_config_init(
|
||||
ma_format_f32, decoder->format.channels, decoder->format.sample_rate);
|
||||
|
||||
if (ma_decoder_init_file(path, &cfg, &decoder->ma) != MA_SUCCESS) {
|
||||
fprintf(stderr, "jukebox: invalid audio file '%s'\n", path);
|
||||
abort();
|
||||
}
|
||||
return decoder;
|
||||
}
|
||||
|
||||
jukebox_decoder_t* jukebox_decoder_new_from_memory_mp3(
|
||||
const jukebox_format_t* format, const void* buf, size_t len) {
|
||||
assert(jukebox_format_valid(format));
|
||||
assert(buf != NULL || len == 0);
|
||||
|
||||
jukebox_decoder_t* decoder = jukebox_decoder_new_(format);
|
||||
|
||||
const ma_decoder_config cfg = ma_decoder_config_init(
|
||||
ma_format_f32, decoder->format.channels, decoder->format.sample_rate);
|
||||
|
||||
if (ma_decoder_init_memory_mp3(buf, len, &cfg, &decoder->ma) != MA_SUCCESS) {
|
||||
fprintf(stderr, "jukebox: invalid mp3 buffer\n");
|
||||
abort();
|
||||
}
|
||||
return decoder;
|
||||
}
|
||||
|
||||
void jukebox_decoder_delete(jukebox_decoder_t* decoder) {
|
||||
if (decoder == NULL) return;
|
||||
|
||||
ma_decoder_uninit(&decoder->ma);
|
||||
|
||||
memory_delete(decoder);
|
||||
}
|
||||
|
||||
void jukebox_decoder_play(
|
||||
jukebox_decoder_t* decoder, const rational_t* st, bool loop) {
|
||||
assert(decoder != NULL);
|
||||
assert(rational_valid(st));
|
||||
|
||||
if (atomic_load(&decoder->playing)) return;
|
||||
|
||||
rational_t t = *st;
|
||||
rational_normalize(&t, decoder->format.sample_rate);
|
||||
|
||||
atomic_store(&decoder->duration, INT64_MAX);
|
||||
|
||||
decoder->loop = loop;
|
||||
decoder->request_seek = true;
|
||||
|
||||
atomic_store(&decoder->current, t.num);
|
||||
atomic_store(&decoder->playing, true);
|
||||
}
|
||||
|
||||
void jukebox_decoder_resume(jukebox_decoder_t* decoder, bool loop) {
|
||||
assert(decoder != NULL);
|
||||
|
||||
if (atomic_load(&decoder->playing)) return;
|
||||
|
||||
atomic_store(&decoder->duration, INT64_MAX);
|
||||
|
||||
decoder->loop = loop;
|
||||
|
||||
atomic_store(&decoder->playing, true);
|
||||
}
|
||||
|
||||
void jukebox_decoder_stop_after(
|
||||
jukebox_decoder_t* decoder, const rational_t* dur) {
|
||||
assert(decoder != NULL);
|
||||
assert(rational_valid(dur));
|
||||
|
||||
if (!atomic_load(&decoder->playing)) return;
|
||||
|
||||
rational_t d = *dur;
|
||||
rational_normalize(&d, decoder->format.sample_rate);
|
||||
|
||||
atomic_store(&decoder->duration, d.num);
|
||||
}
|
||||
|
||||
void jukebox_decoder_get_seek_position(
|
||||
const jukebox_decoder_t* decoder, rational_t* time) {
|
||||
assert(decoder != NULL);
|
||||
assert(time != NULL);
|
||||
|
||||
*time = (typeof(*time)) {
|
||||
.num = atomic_load(&decoder->current),
|
||||
.den = decoder->format.sample_rate,
|
||||
};
|
||||
}
|
||||
|
||||
void jukebox_decoder_get_duration(
|
||||
const jukebox_decoder_t* decoder, rational_t* time) {
|
||||
assert(decoder != NULL);
|
||||
assert(time != NULL);
|
||||
|
||||
*time = rational(
|
||||
ma_decoder_get_length_in_pcm_frames((ma_decoder*) &decoder->ma),
|
||||
decoder->format.sample_rate);
|
||||
}
|
71
util/jukebox/decoder.h
Normal file
71
util/jukebox/decoder.h
Normal file
@@ -0,0 +1,71 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "util/math/rational.h"
|
||||
|
||||
#include "./format.h"
|
||||
|
||||
struct jukebox_decoder_t;
|
||||
typedef struct jukebox_decoder_t jukebox_decoder_t;
|
||||
|
||||
jukebox_decoder_t* /* OWNERSHIP */
|
||||
jukebox_decoder_new_from_file(
|
||||
const jukebox_format_t* format,
|
||||
const char* path
|
||||
);
|
||||
|
||||
jukebox_decoder_t* /* OWNERSHIP */
|
||||
jukebox_decoder_new_from_memory_mp3(
|
||||
const jukebox_format_t* format,
|
||||
const void* buf,
|
||||
size_t len
|
||||
);
|
||||
|
||||
void
|
||||
jukebox_decoder_delete(
|
||||
jukebox_decoder_t* decoder /* OWNERSHIP */
|
||||
);
|
||||
|
||||
void
|
||||
jukebox_decoder_play(
|
||||
jukebox_decoder_t* decoder,
|
||||
const rational_t* st,
|
||||
bool loop
|
||||
);
|
||||
|
||||
void
|
||||
jukebox_decoder_resume(
|
||||
jukebox_decoder_t* decoder,
|
||||
bool loop
|
||||
);
|
||||
|
||||
void
|
||||
jukebox_decoder_stop_after(
|
||||
jukebox_decoder_t* decoder,
|
||||
const rational_t* dur
|
||||
);
|
||||
|
||||
void
|
||||
jukebox_decoder_set_loop(
|
||||
jukebox_decoder_t* decoder,
|
||||
const rational_t* start, /* NULLABLE */
|
||||
const rational_t* end /* NULLABLE */
|
||||
);
|
||||
|
||||
void
|
||||
jukebox_decoder_unset_loop(
|
||||
jukebox_decoder_t* decoder
|
||||
);
|
||||
|
||||
void
|
||||
jukebox_decoder_get_seek_position(
|
||||
const jukebox_decoder_t* decoder,
|
||||
rational_t* time
|
||||
);
|
||||
|
||||
void
|
||||
jukebox_decoder_get_duration(
|
||||
const jukebox_decoder_t* decoder,
|
||||
rational_t* time
|
||||
);
|
94
util/jukebox/delay.c
Normal file
94
util/jukebox/delay.c
Normal file
@@ -0,0 +1,94 @@
|
||||
#include "./delay.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "util/math/algorithm.h"
|
||||
#include "util/math/rational.h"
|
||||
#include "util/memory/memory.h"
|
||||
|
||||
#include "./effect.h"
|
||||
#include "./format.h"
|
||||
|
||||
struct jukebox_delay_t {
|
||||
jukebox_effect_t super;
|
||||
jukebox_format_t format;
|
||||
|
||||
float source;
|
||||
float feedback;
|
||||
|
||||
size_t length;
|
||||
size_t offset;
|
||||
size_t cursor;
|
||||
float buffer[1];
|
||||
};
|
||||
|
||||
static void jukebox_delay_affect_(
|
||||
jukebox_effect_t* effect, const jukebox_effect_pcm_t* pcm) {
|
||||
assert(effect != NULL);
|
||||
assert(pcm != NULL);
|
||||
|
||||
jukebox_delay_t* d = (typeof(d)) effect;
|
||||
|
||||
const int32_t ch = d->format.channels;
|
||||
const size_t pcmlen = pcm->frames*ch;
|
||||
|
||||
for (size_t i = 0; i < pcmlen; ++i) {
|
||||
float* bufoff = &d->buffer[d->offset];
|
||||
float* bufcur = &d->buffer[d->cursor];
|
||||
float* pcmptr = &pcm->ptr[i];
|
||||
|
||||
*bufoff *= d->feedback;
|
||||
*bufoff += *pcmptr * d->source;
|
||||
|
||||
*pcmptr += *bufcur;
|
||||
if (MATH_ABS(*pcmptr) >= 1.0f) {
|
||||
*pcmptr = MATH_SIGN(*pcmptr) * 0.99f;
|
||||
}
|
||||
|
||||
if (++d->offset >= d->length) d->offset = 0;
|
||||
if (++d->cursor >= d->length) d->cursor = 0;
|
||||
}
|
||||
}
|
||||
|
||||
jukebox_delay_t* jukebox_delay_new(
|
||||
const jukebox_format_t* format,
|
||||
const rational_t* duration,
|
||||
float source_attenuation,
|
||||
float feedback_attenuation) {
|
||||
assert(jukebox_format_valid(format));
|
||||
assert(rational_valid(duration));
|
||||
assert(MATH_FLOAT_VALID(source_attenuation));
|
||||
assert(MATH_FLOAT_VALID(feedback_attenuation));
|
||||
|
||||
rational_t dur_r = rational(format->channels*duration->num, duration->den);
|
||||
rational_normalize(&dur_r, format->sample_rate);
|
||||
|
||||
const size_t length = dur_r.num;
|
||||
|
||||
jukebox_delay_t* d =
|
||||
memory_new(sizeof(*d) + (length-1)*sizeof(d->buffer[0]));
|
||||
*d = (typeof(*d)) {
|
||||
.super = {
|
||||
.vtable = {
|
||||
.affect = jukebox_delay_affect_,
|
||||
},
|
||||
},
|
||||
.format = *format,
|
||||
.source = source_attenuation,
|
||||
.feedback = feedback_attenuation,
|
||||
.length = length,
|
||||
.offset = dur_r.num,
|
||||
.cursor = 0,
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < d->length; ++i) d->buffer[i] = 0;
|
||||
return d;
|
||||
}
|
||||
|
||||
void jukebox_delay_delete(jukebox_delay_t* delay) {
|
||||
if (delay == NULL) return;
|
||||
|
||||
memory_delete(delay);
|
||||
}
|
23
util/jukebox/delay.h
Normal file
23
util/jukebox/delay.h
Normal file
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "util/math/rational.h"
|
||||
|
||||
#include "./format.h"
|
||||
|
||||
struct jukebox_delay_t;
|
||||
typedef struct jukebox_delay_t jukebox_delay_t;
|
||||
|
||||
jukebox_delay_t*
|
||||
jukebox_delay_new(
|
||||
const jukebox_format_t* format,
|
||||
const rational_t* duration,
|
||||
float source_attenuation,
|
||||
float feedback_attenuation
|
||||
);
|
||||
|
||||
void
|
||||
jukebox_delay_delete(
|
||||
jukebox_delay_t* delay
|
||||
);
|
13
util/jukebox/effect.c
Normal file
13
util/jukebox/effect.c
Normal file
@@ -0,0 +1,13 @@
|
||||
#include "./effect.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
|
||||
void jukebox_effect_affect(
|
||||
jukebox_effect_t* snd, const jukebox_effect_pcm_t* pcm) {
|
||||
assert(snd != NULL);
|
||||
assert(pcm != NULL);
|
||||
|
||||
assert(snd->vtable.affect != NULL);
|
||||
snd->vtable.affect(snd, pcm);
|
||||
}
|
31
util/jukebox/effect.h
Normal file
31
util/jukebox/effect.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "./format.h"
|
||||
|
||||
struct jukebox_effect_t;
|
||||
typedef struct jukebox_effect_t jukebox_effect_t;
|
||||
|
||||
typedef struct {
|
||||
float* ptr;
|
||||
int32_t frames;
|
||||
} jukebox_effect_pcm_t;
|
||||
|
||||
typedef struct {
|
||||
void
|
||||
(*affect)(
|
||||
jukebox_effect_t* snd,
|
||||
const jukebox_effect_pcm_t* pcm
|
||||
);
|
||||
} jukebox_effect_vtable_t;
|
||||
|
||||
struct jukebox_effect_t {
|
||||
jukebox_effect_vtable_t vtable;
|
||||
};
|
||||
|
||||
void
|
||||
jukebox_effect_affect(
|
||||
jukebox_effect_t* snd,
|
||||
const jukebox_effect_pcm_t* pcm
|
||||
);
|
9
util/jukebox/format.c
Normal file
9
util/jukebox/format.c
Normal file
@@ -0,0 +1,9 @@
|
||||
#include "./format.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
bool jukebox_format_valid(const jukebox_format_t* format) {
|
||||
return format != NULL && format->sample_rate > 0 && format->channels > 0;
|
||||
}
|
14
util/jukebox/format.h
Normal file
14
util/jukebox/format.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct {
|
||||
int32_t sample_rate;
|
||||
int32_t channels;
|
||||
} jukebox_format_t;
|
||||
|
||||
bool
|
||||
jukebox_format_valid(
|
||||
const jukebox_format_t* format
|
||||
);
|
57
util/jukebox/lowpass.c
Normal file
57
util/jukebox/lowpass.c
Normal file
@@ -0,0 +1,57 @@
|
||||
#include "./lowpass.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include "util/math/algorithm.h"
|
||||
|
||||
#include "./effect.h"
|
||||
#include "./format.h"
|
||||
|
||||
static void jukebox_lowpass_affect_(
|
||||
jukebox_effect_t* effect, const jukebox_effect_pcm_t* pcm) {
|
||||
assert(effect != NULL);
|
||||
assert(pcm != NULL);
|
||||
|
||||
jukebox_lowpass_t* lp = (typeof(lp)) effect;
|
||||
|
||||
const size_t ch = lp->format.channels;
|
||||
const float f = atomic_load(&lp->factor) * JUKEBOX_LOWPASS_FACTOR_UNIT;
|
||||
|
||||
assert(ch <= JUKEBOX_LOWPASS_SUPPORT_MAX_CHANNELS);
|
||||
|
||||
const size_t pcmlen = pcm->frames * ch;
|
||||
for (size_t i = 0; i < pcmlen; ++i) {
|
||||
const float p = (ch+1 <= i? pcm->ptr[i-1-ch]: lp->prev[i]);
|
||||
pcm->ptr[i] = (1.0f-f)*pcm->ptr[i] + f*p;
|
||||
}
|
||||
|
||||
if (pcmlen < ch) return;
|
||||
for (size_t i = 0; i < ch; ++i) {
|
||||
lp->prev[i] = pcm->ptr[pcmlen-ch+i];
|
||||
}
|
||||
}
|
||||
|
||||
void jukebox_lowpass_initialize(
|
||||
jukebox_lowpass_t* lowpass, const jukebox_format_t* format, float factor) {
|
||||
assert(lowpass != NULL);
|
||||
assert(jukebox_format_valid(format));
|
||||
assert(MATH_FLOAT_VALID(factor));
|
||||
|
||||
*lowpass = (typeof(*lowpass)) {
|
||||
.super = {
|
||||
.vtable = {
|
||||
.affect = jukebox_lowpass_affect_,
|
||||
},
|
||||
},
|
||||
.format = *format,
|
||||
.factor = factor*10000,
|
||||
.prev = {0},
|
||||
};
|
||||
}
|
||||
|
||||
void jukebox_lowpass_deinitialize(jukebox_lowpass_t* lowpass) {
|
||||
assert(lowpass != NULL);
|
||||
|
||||
}
|
31
util/jukebox/lowpass.h
Normal file
31
util/jukebox/lowpass.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdatomic.h>
|
||||
|
||||
#include "./effect.h"
|
||||
#include "./format.h"
|
||||
|
||||
#define JUKEBOX_LOWPASS_SUPPORT_MAX_CHANNELS 16
|
||||
|
||||
#define JUKEBOX_LOWPASS_FACTOR_UNIT (1.0f/10000)
|
||||
|
||||
typedef struct {
|
||||
jukebox_effect_t super;
|
||||
jukebox_format_t format;
|
||||
|
||||
atomic_uint_least32_t factor;
|
||||
|
||||
float prev[JUKEBOX_LOWPASS_SUPPORT_MAX_CHANNELS];
|
||||
} jukebox_lowpass_t;
|
||||
|
||||
void
|
||||
jukebox_lowpass_initialize(
|
||||
jukebox_lowpass_t* lowpass,
|
||||
const jukebox_format_t* format,
|
||||
float factor
|
||||
);
|
||||
|
||||
void
|
||||
jukebox_lowpass_deinitialize(
|
||||
jukebox_lowpass_t* lowpass
|
||||
);
|
98
util/jukebox/mixer.c
Normal file
98
util/jukebox/mixer.c
Normal file
@@ -0,0 +1,98 @@
|
||||
#include "./mixer.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "thirdparty/miniaudio/miniaudio.h"
|
||||
|
||||
#include "util/container/array.h"
|
||||
#include "util/memory/memory.h"
|
||||
|
||||
#include "./effect.h"
|
||||
|
||||
struct jukebox_mixer_t {
|
||||
ma_device device;
|
||||
|
||||
jukebox_format_t format;
|
||||
|
||||
size_t effects_reserved;
|
||||
size_t effects_length;
|
||||
jukebox_effect_t* effects[1];
|
||||
};
|
||||
|
||||
static void jukebox_mixer_device_callback_(
|
||||
ma_device* device, void* out, const void* in, ma_uint32 frames) {
|
||||
assert(device != NULL);
|
||||
assert(out != NULL);
|
||||
assert(frames > 0);
|
||||
|
||||
jukebox_mixer_t* mixer = (typeof(mixer)) device->pUserData;
|
||||
assert(mixer != NULL);
|
||||
|
||||
const jukebox_effect_pcm_t pcm = {
|
||||
.ptr = out,
|
||||
.frames = frames,
|
||||
};
|
||||
for (size_t i = 0; i < mixer->effects_length; ++i) {
|
||||
jukebox_effect_affect(mixer->effects[i], &pcm);
|
||||
}
|
||||
|
||||
(void) in;
|
||||
}
|
||||
|
||||
jukebox_mixer_t* jukebox_mixer_new(
|
||||
const jukebox_format_t* format, size_t reserve) {
|
||||
assert(jukebox_format_valid(format));
|
||||
assert(reserve > 0);
|
||||
|
||||
jukebox_mixer_t* mixer = memory_new(
|
||||
sizeof(*mixer) + (reserve-1)*sizeof(mixer->effects[0]));
|
||||
*mixer = (typeof(*mixer)) {
|
||||
.format = *format,
|
||||
.effects_reserved = reserve,
|
||||
};
|
||||
|
||||
ma_device_config config = ma_device_config_init(ma_device_type_playback);
|
||||
config.playback.format = ma_format_f32;
|
||||
config.playback.channels = mixer->format.channels;
|
||||
config.sampleRate = mixer->format.sample_rate;
|
||||
config.dataCallback = jukebox_mixer_device_callback_;
|
||||
config.pUserData = mixer;
|
||||
|
||||
if (ma_device_init(NULL, &config, &mixer->device) != MA_SUCCESS) {
|
||||
fprintf(stderr, "failed to open audio device\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
if (ma_device_start(&mixer->device) != MA_SUCCESS) {
|
||||
fprintf(stderr, "failed to start audio device\n");
|
||||
ma_device_uninit(&mixer->device);
|
||||
abort();
|
||||
}
|
||||
|
||||
return mixer;
|
||||
}
|
||||
|
||||
void jukebox_mixer_delete(jukebox_mixer_t* mixer) {
|
||||
if (mixer == NULL) return;
|
||||
|
||||
ma_device_uninit(&mixer->device);
|
||||
|
||||
memory_delete(mixer);
|
||||
}
|
||||
|
||||
void jukebox_mixer_add_effect(
|
||||
jukebox_mixer_t* mixer, jukebox_effect_t* effect) {
|
||||
assert(mixer != NULL);
|
||||
assert(effect != NULL);
|
||||
|
||||
ma_mutex_lock(&mixer->device.lock);
|
||||
if (mixer->effects_length >= mixer->effects_reserved) {
|
||||
fprintf(stderr, "mixer effects overflow\n");
|
||||
abort();
|
||||
}
|
||||
mixer->effects[mixer->effects_length++] = effect;
|
||||
ma_mutex_unlock(&mixer->device.lock);
|
||||
}
|
26
util/jukebox/mixer.h
Normal file
26
util/jukebox/mixer.h
Normal file
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "./effect.h"
|
||||
#include "./format.h"
|
||||
|
||||
struct jukebox_mixer_t;
|
||||
typedef struct jukebox_mixer_t jukebox_mixer_t;
|
||||
|
||||
jukebox_mixer_t* /* OWNERSHIP */
|
||||
jukebox_mixer_new(
|
||||
const jukebox_format_t* format,
|
||||
size_t reserve
|
||||
);
|
||||
|
||||
void
|
||||
jukebox_mixer_delete(
|
||||
jukebox_mixer_t* mixer /* OWNERSHIP */
|
||||
);
|
||||
|
||||
void
|
||||
jukebox_mixer_add_effect(
|
||||
jukebox_mixer_t* mixer,
|
||||
jukebox_effect_t* effect /* must be alive while the mixer is alive */
|
||||
);
|
180
util/jukebox/sound.c
Normal file
180
util/jukebox/sound.c
Normal file
@@ -0,0 +1,180 @@
|
||||
#include "./sound.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "thirdparty/miniaudio/miniaudio.h"
|
||||
|
||||
#include "util/math/rational.h"
|
||||
#include "util/memory/memory.h"
|
||||
|
||||
#include "./effect.h"
|
||||
#include "./format.h"
|
||||
|
||||
struct jukebox_sound_buffer_t {
|
||||
jukebox_format_t format;
|
||||
|
||||
uint64_t frames;
|
||||
float ptr[1];
|
||||
};
|
||||
|
||||
struct jukebox_sound_t {
|
||||
jukebox_effect_t super;
|
||||
|
||||
const jukebox_sound_buffer_t* buffer;
|
||||
|
||||
atomic_bool stop_all;
|
||||
|
||||
size_t length;
|
||||
atomic_uint_least64_t frames[1];
|
||||
};
|
||||
|
||||
static void jukebox_sound_affect_(
|
||||
jukebox_effect_t* effect, const jukebox_effect_pcm_t* pcm) {
|
||||
assert(effect != NULL);
|
||||
assert(pcm != NULL);
|
||||
|
||||
jukebox_sound_t* s = (typeof(s)) effect;
|
||||
|
||||
if (atomic_load(&s->stop_all)) {
|
||||
for (size_t i = 0; i < s->length; ++i) {
|
||||
atomic_store(&s->frames[i], s->buffer->frames);
|
||||
}
|
||||
atomic_store(&s->stop_all, false);
|
||||
return;
|
||||
}
|
||||
|
||||
const int32_t ch = s->buffer->format.channels;
|
||||
for (size_t i = 0; i < s->length; ++i) {
|
||||
const uint64_t frame = atomic_load(&s->frames[i]);
|
||||
if (frame >= s->buffer->frames) continue;
|
||||
|
||||
const float* src = s->buffer->ptr;
|
||||
float* dst = pcm->ptr;
|
||||
|
||||
const float* src_ed = src + s->buffer->frames*ch;
|
||||
const float* dst_ed = dst + pcm->frames*ch;
|
||||
|
||||
src += frame*ch;
|
||||
while (src < src_ed && dst < dst_ed) {
|
||||
*(dst++) += *(src++);
|
||||
}
|
||||
atomic_fetch_add(&s->frames[i], (src - s->buffer->ptr)/ch - frame);
|
||||
}
|
||||
}
|
||||
|
||||
static jukebox_sound_buffer_t* jukebox_sound_buffer_new_(
|
||||
const jukebox_format_t* format, ma_decoder* decoder) {
|
||||
assert(format != NULL);
|
||||
assert(decoder != NULL);
|
||||
|
||||
const uint64_t frames = ma_decoder_get_length_in_pcm_frames(decoder);
|
||||
const size_t length = frames * format->channels;
|
||||
|
||||
jukebox_sound_buffer_t* buf =
|
||||
memory_new(sizeof(*buf) + (length-1)*sizeof(buf->ptr[0]));
|
||||
*buf = (typeof(*buf)) {
|
||||
.format = *format,
|
||||
.frames = frames,
|
||||
};
|
||||
|
||||
const uint64_t read_frames =
|
||||
ma_decoder_read_pcm_frames(decoder, buf->ptr, frames);
|
||||
|
||||
/* usually read_frames should be equal to frames */
|
||||
buf->frames = read_frames;
|
||||
return buf;
|
||||
}
|
||||
|
||||
jukebox_sound_buffer_t* jukebox_sound_buffer_new_from_file(
|
||||
const jukebox_format_t* format, const char* path) {
|
||||
assert(jukebox_format_valid(format));
|
||||
|
||||
const ma_decoder_config cfg = ma_decoder_config_init(
|
||||
ma_format_f32, format->channels, format->sample_rate);
|
||||
|
||||
ma_decoder decoder;
|
||||
if (ma_decoder_init_file(path, &cfg, &decoder) != MA_SUCCESS) {
|
||||
fprintf(stderr, "jukebox: invalid audio file '%s'\n", path);
|
||||
abort();
|
||||
}
|
||||
jukebox_sound_buffer_t* sndbuf = jukebox_sound_buffer_new_(format, &decoder);
|
||||
ma_decoder_uninit(&decoder);
|
||||
return sndbuf;
|
||||
}
|
||||
|
||||
jukebox_sound_buffer_t* jukebox_sound_buffer_new_from_memory_mp3(
|
||||
const jukebox_format_t* format, const void* buf, size_t len) {
|
||||
assert(jukebox_format_valid(format));
|
||||
assert(buf != NULL || len == 0);
|
||||
|
||||
const ma_decoder_config cfg = ma_decoder_config_init(
|
||||
ma_format_f32, format->channels, format->sample_rate);
|
||||
|
||||
ma_decoder decoder;
|
||||
if (ma_decoder_init_memory_mp3(buf, len, &cfg, &decoder) != MA_SUCCESS) {
|
||||
fprintf(stderr, "jukebox: invalid mp3 buffer\n");
|
||||
abort();
|
||||
}
|
||||
jukebox_sound_buffer_t* sndbuf = jukebox_sound_buffer_new_(format, &decoder);
|
||||
ma_decoder_uninit(&decoder);
|
||||
return sndbuf;
|
||||
}
|
||||
|
||||
void jukebox_sound_buffer_delete(jukebox_sound_buffer_t* buf) {
|
||||
if (buf == NULL) return;
|
||||
|
||||
memory_delete(buf);
|
||||
}
|
||||
|
||||
jukebox_sound_t* jukebox_sound_new(
|
||||
const jukebox_sound_buffer_t* buf, size_t max_concurrent) {
|
||||
assert(buf != NULL);
|
||||
assert(max_concurrent > 0);
|
||||
|
||||
jukebox_sound_t* sound =
|
||||
memory_new(sizeof(*sound) + (max_concurrent-1)*sizeof(sound->frames[0]));
|
||||
*sound = (typeof(*sound)) {
|
||||
.super = {
|
||||
.vtable = {
|
||||
.affect = jukebox_sound_affect_,
|
||||
},
|
||||
},
|
||||
.buffer = buf,
|
||||
.length = max_concurrent,
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < sound->length; ++i) {
|
||||
sound->frames[i] = sound->buffer->frames;
|
||||
}
|
||||
return sound;
|
||||
}
|
||||
|
||||
void jukebox_sound_delete(jukebox_sound_t* sound) {
|
||||
if (sound == NULL) return;
|
||||
|
||||
memory_delete(sound);
|
||||
}
|
||||
|
||||
bool jukebox_sound_play(jukebox_sound_t* sound) {
|
||||
assert(sound != NULL);
|
||||
|
||||
if (atomic_load(&sound->stop_all)) return false;
|
||||
|
||||
for (size_t i = 0; i < sound->length; ++i) {
|
||||
if (atomic_load(&sound->frames[i]) >= sound->buffer->frames) {
|
||||
atomic_store(&sound->frames[i], 0);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void jukebox_sound_stop_all(jukebox_sound_t* sound) {
|
||||
assert(sound != NULL);
|
||||
|
||||
atomic_store(&sound->stop_all, true);
|
||||
}
|
53
util/jukebox/sound.h
Normal file
53
util/jukebox/sound.h
Normal file
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdatomic.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include "util/math/rational.h"
|
||||
|
||||
#include "./format.h"
|
||||
|
||||
struct jukebox_sound_buffer_t;
|
||||
typedef struct jukebox_sound_buffer_t jukebox_sound_buffer_t;
|
||||
|
||||
struct jukebox_sound_t;
|
||||
typedef struct jukebox_sound_t jukebox_sound_t;
|
||||
|
||||
jukebox_sound_buffer_t*
|
||||
jukebox_sound_buffer_new_from_file(
|
||||
const jukebox_format_t* format,
|
||||
const char* path
|
||||
);
|
||||
|
||||
jukebox_sound_buffer_t*
|
||||
jukebox_sound_buffer_new_from_memory_mp3(
|
||||
const jukebox_format_t* format,
|
||||
const void* buf,
|
||||
size_t len
|
||||
);
|
||||
|
||||
void
|
||||
jukebox_sound_buffer_delete(
|
||||
jukebox_sound_buffer_t* buf
|
||||
);
|
||||
|
||||
jukebox_sound_t*
|
||||
jukebox_sound_new(
|
||||
const jukebox_sound_buffer_t* buf,
|
||||
size_t max_concurrent
|
||||
);
|
||||
|
||||
void
|
||||
jukebox_sound_delete(
|
||||
jukebox_sound_t* sound
|
||||
);
|
||||
|
||||
bool
|
||||
jukebox_sound_play(
|
||||
jukebox_sound_t* sound
|
||||
);
|
||||
|
||||
void
|
||||
jukebox_sound_stop_all(
|
||||
jukebox_sound_t* sound
|
||||
);
|
53
util/jukebox/test.c
Normal file
53
util/jukebox/test.c
Normal file
@@ -0,0 +1,53 @@
|
||||
#undef NDEBUG
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "util/math/rational.h"
|
||||
|
||||
#include "./delay.h"
|
||||
#include "./effect.h"
|
||||
#include "./lowpass.h"
|
||||
#include "./mixer.h"
|
||||
#include "./sound.h"
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
assert(argc == 2);
|
||||
|
||||
static const jukebox_format_t format = {
|
||||
.sample_rate = 48000,
|
||||
.channels = 2,
|
||||
};
|
||||
|
||||
jukebox_sound_buffer_t* sndbuf =
|
||||
jukebox_sound_buffer_new_from_file(&format, argv[1]);
|
||||
|
||||
jukebox_sound_t* snd = jukebox_sound_new(sndbuf, 16);
|
||||
|
||||
static const rational_t delay_dur = rational(1, 10);
|
||||
jukebox_delay_t* delay = jukebox_delay_new(&format, &delay_dur, 0.1f, 0.5f);
|
||||
|
||||
jukebox_lowpass_t lowpass = {0};
|
||||
jukebox_lowpass_initialize(&lowpass, &format, 0.01f);
|
||||
|
||||
jukebox_mixer_t* mixer = jukebox_mixer_new(&format, 16);
|
||||
jukebox_mixer_add_effect(mixer, (jukebox_effect_t*) snd);
|
||||
jukebox_mixer_add_effect(mixer, (jukebox_effect_t*) delay);
|
||||
jukebox_mixer_add_effect(mixer, &lowpass.super);
|
||||
|
||||
for (size_t i = 0; i < 32; ++i) {
|
||||
jukebox_sound_play(snd);
|
||||
printf("press enter to continue...\n");
|
||||
getchar();
|
||||
}
|
||||
jukebox_sound_stop_all(snd);
|
||||
|
||||
jukebox_mixer_delete(mixer);
|
||||
jukebox_lowpass_deinitialize(&lowpass);
|
||||
jukebox_delay_delete(delay);
|
||||
jukebox_sound_delete(snd);
|
||||
jukebox_sound_buffer_delete(sndbuf);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
16
util/math/CMakeLists.txt
Normal file
16
util/math/CMakeLists.txt
Normal file
@@ -0,0 +1,16 @@
|
||||
add_library(math
|
||||
rational.c
|
||||
vector.c
|
||||
)
|
||||
target_source_of_source(math
|
||||
algorithm.sos.c
|
||||
matrix.sos.c
|
||||
)
|
||||
target_link_libraries(math m)
|
||||
|
||||
if (BUILD_TESTING)
|
||||
add_executable(math-test test.c)
|
||||
target_link_libraries(math-test math)
|
||||
|
||||
add_test(math-test math-test)
|
||||
endif()
|
63
util/math/algorithm.h
Normal file
63
util/math/algorithm.h
Normal file
@@ -0,0 +1,63 @@
|
||||
#pragma once
|
||||
|
||||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "./constant.h"
|
||||
|
||||
#define MATH_ABS(a) ((a) > 0? (a): -(a))
|
||||
|
||||
#define MATH_MAX(a, b) ((a) > (b)? (a): (b))
|
||||
#define MATH_MIN(a, b) ((a) < (b)? (a): (b))
|
||||
|
||||
#define MATH_DIFF(a, b) (MATH_MAX(a, b) - MATH_MIN(a, b))
|
||||
|
||||
#define MATH_CLAMP(x, min, max) ((x) < (min)? (min): (x) > (max)? (max): (x))
|
||||
|
||||
#define MATH_SIGN(a) (a < 0? -1: a > 0? 1: 0)
|
||||
|
||||
#define MATH_FLOAT_EQUAL(a, b) (MATH_ABS((a) - (b)) < MATH_EPSILON)
|
||||
#define MATH_FLOAT_VALID(a) (!isnan(a) && !isinf(a))
|
||||
|
||||
|
||||
#define decl_next_power2_(type) \
|
||||
type##_t math_##type##_next_power2(type##_t x)
|
||||
|
||||
decl_next_power2_(int8);
|
||||
decl_next_power2_(int16);
|
||||
decl_next_power2_(int32);
|
||||
decl_next_power2_(int64);
|
||||
decl_next_power2_(uint8);
|
||||
decl_next_power2_(uint16);
|
||||
decl_next_power2_(uint32);
|
||||
decl_next_power2_(uint64);
|
||||
|
||||
#undef decl_next_power2_
|
||||
|
||||
#define decl_gcd_(type) \
|
||||
type##_t math_##type##_gcd(type##_t x, type##_t y)
|
||||
|
||||
decl_gcd_(int8);
|
||||
decl_gcd_(int16);
|
||||
decl_gcd_(int32);
|
||||
decl_gcd_(int64);
|
||||
decl_gcd_(uint8);
|
||||
decl_gcd_(uint16);
|
||||
decl_gcd_(uint32);
|
||||
decl_gcd_(uint64);
|
||||
|
||||
#undef decl_gcd_
|
||||
|
||||
#define decl_lcm_(type) \
|
||||
type##_t math_##type##_lcm(type##_t x, type##_t y)
|
||||
|
||||
decl_lcm_(int8);
|
||||
decl_lcm_(int16);
|
||||
decl_lcm_(int32);
|
||||
decl_lcm_(int64);
|
||||
decl_lcm_(uint8);
|
||||
decl_lcm_(uint16);
|
||||
decl_lcm_(uint32);
|
||||
decl_lcm_(uint64);
|
||||
|
||||
#undef decl_lcm_
|
88
util/math/algorithm.sos.c
Normal file
88
util/math/algorithm.sos.c
Normal file
@@ -0,0 +1,88 @@
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static void next_power2(const char* type, size_t N) {
|
||||
assert(N > 0);
|
||||
|
||||
/* https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 */
|
||||
|
||||
printf("%s%zu_t math_%s%zu_next_power2(%s%zu_t x) {", type, N, type, N, type, N);
|
||||
|
||||
printf("--x;");
|
||||
for (size_t i = 1; i < N; i*=2) {
|
||||
printf("x |= x >> %zu;", i);
|
||||
}
|
||||
printf("return ++x;");
|
||||
printf("}\n");
|
||||
}
|
||||
|
||||
static void gcd(const char* type, size_t N) {
|
||||
assert(N > 0);
|
||||
|
||||
printf("%s%zu_t math_%s%zu_gcd(%s%zu_t x, %s%zu_t y) {", type, N, type, N, type, N, type, N);
|
||||
|
||||
printf("assert(x > 0);");
|
||||
printf("assert(y > 0);");
|
||||
|
||||
printf("if (x < y) return math_%s%zu_gcd(y, x);", type, N);
|
||||
|
||||
printf("%s%zu_t z;", type, N);
|
||||
|
||||
printf("while (y) {");
|
||||
printf("z = x%%y;");
|
||||
printf("x = y;");
|
||||
printf("y = z;");
|
||||
printf("}");
|
||||
|
||||
printf("return x;}\n");
|
||||
}
|
||||
|
||||
static void lcm(const char* type, size_t N) {
|
||||
assert(N > 0);
|
||||
|
||||
printf("%s%zu_t math_%s%zu_lcm(%s%zu_t x, %s%zu_t y) {", type, N, type, N, type, N, type, N);
|
||||
|
||||
printf("assert(x > 0);");
|
||||
printf("assert(y > 0);");
|
||||
|
||||
printf("return x / math_%s%zu_gcd(x, y) * y;}\n", type, N);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
(void) argc, (void) argv;
|
||||
|
||||
printf("#include \"./algorithm.h\"\n");
|
||||
|
||||
printf("#include <assert.h>\n");
|
||||
printf("#include <stdint.h>\n");
|
||||
|
||||
next_power2("int", 8);
|
||||
next_power2("int", 16);
|
||||
next_power2("int", 32);
|
||||
next_power2("int", 64);
|
||||
next_power2("uint", 8);
|
||||
next_power2("uint", 16);
|
||||
next_power2("uint", 32);
|
||||
next_power2("uint", 64);
|
||||
|
||||
gcd("int", 8);
|
||||
gcd("int", 16);
|
||||
gcd("int", 32);
|
||||
gcd("int", 64);
|
||||
gcd("uint", 8);
|
||||
gcd("uint", 16);
|
||||
gcd("uint", 32);
|
||||
gcd("uint", 64);
|
||||
|
||||
lcm("int", 8);
|
||||
lcm("int", 16);
|
||||
lcm("int", 32);
|
||||
lcm("int", 64);
|
||||
lcm("uint", 8);
|
||||
lcm("uint", 16);
|
||||
lcm("uint", 32);
|
||||
lcm("uint", 64);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
8
util/math/constant.h
Normal file
8
util/math/constant.h
Normal file
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <math.h>
|
||||
#include <float.h>
|
||||
|
||||
#define MATH_EPSILON FLT_EPSILON
|
||||
#define MATH_INF 1e+10
|
||||
#define MATH_PI M_PI
|
123
util/math/matrix.h
Normal file
123
util/math/matrix.h
Normal file
@@ -0,0 +1,123 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include "./vector.h"
|
||||
|
||||
typedef union {
|
||||
vec2_t col[2];
|
||||
float elm[2][2];
|
||||
float ptr[4];
|
||||
} mat2_t;
|
||||
|
||||
typedef union {
|
||||
vec3_t col[3];
|
||||
float elm[3][3];
|
||||
float ptr[9];
|
||||
} mat3_t;
|
||||
|
||||
typedef union {
|
||||
vec4_t col[4];
|
||||
float elm[4][4];
|
||||
float ptr[16];
|
||||
} mat4_t;
|
||||
|
||||
#define mat3_identity() (mat3_t) {{ \
|
||||
vec3(1, 0, 0), \
|
||||
vec3(0, 1, 0), \
|
||||
vec3(0, 0, 1), \
|
||||
}}
|
||||
#define mat4_identity() (mat4_t) {{ \
|
||||
vec4(1, 0, 0, 0), \
|
||||
vec4(0, 1, 0, 0), \
|
||||
vec4(0, 0, 1, 0), \
|
||||
vec4(0, 0, 0, 1), \
|
||||
}}
|
||||
|
||||
#define mat3_translation(x, y) (mat3_t) {{ \
|
||||
vec3(1, 0, 0), \
|
||||
vec3(0, 1, 0), \
|
||||
vec3(x, y, 1), \
|
||||
}}
|
||||
#define mat4_translation(x, y, z) (mat4_t) {{ \
|
||||
vec4(1, 0, 0, 0), \
|
||||
vec4(0, 1, 0, 0), \
|
||||
vec4(0, 0, 1, 0), \
|
||||
vec4(x, y, z, 1), \
|
||||
}}
|
||||
|
||||
#define mat3_scale(x, y) (mat3_t) {{ \
|
||||
vec3(x, 0, 0), \
|
||||
vec3(0, y, 0), \
|
||||
vec3(0, 0, 1), \
|
||||
}}
|
||||
#define mat4_scale(x, y, z) (mat4_t) {{ \
|
||||
vec4(x, 0, 0, 0), \
|
||||
vec4(0, y, 0, 0), \
|
||||
vec4(0, 0, z, 0), \
|
||||
vec4(0, 0, 0, 1), \
|
||||
}}
|
||||
|
||||
#define mat3_rotation_z(sin_theta, cos_theta) (mat3_t) {{ \
|
||||
vec3( cos_theta, sin_theta, 0), \
|
||||
vec3(-(sin_theta), cos_theta, 0), \
|
||||
vec3( 0, 0, 1), \
|
||||
}}
|
||||
|
||||
#define mat4_rotation_x(sin_theta, cos_theta) (mat4_t) {{ \
|
||||
vec4(1, 0, 0, 0), \
|
||||
vec4(0, cos_theta, sin_theta, 0), \
|
||||
vec4(0, -(sin_theta), cos_theta, 0), \
|
||||
vec4(0, 0, 0, 1), \
|
||||
}}
|
||||
#define mat4_rotation_y(sin_theta, cos_theta) (mat4_t) {{ \
|
||||
vec4(cos_theta, 0, -(sin_theta), 0), \
|
||||
vec4( 0, 1, 0, 0), \
|
||||
vec4(sin_theta, 0, cos_theta, 0), \
|
||||
vec4( 0, 0, 0, 1), \
|
||||
}}
|
||||
#define mat4_rotation_z(sin_theta, cos_theta) (mat4_t) {{ \
|
||||
vec4( cos_theta, sin_theta, 0, 0), \
|
||||
vec4(-(sin_theta), cos_theta, 0, 0), \
|
||||
vec4( 0, 0, 1, 0), \
|
||||
vec4( 0, 0, 0, 1), \
|
||||
}}
|
||||
|
||||
bool mat2_valid(const mat2_t* x);
|
||||
bool mat3_valid(const mat3_t* x);
|
||||
bool mat4_valid(const mat4_t* x);
|
||||
|
||||
void mat2_add(mat2_t* x, const mat2_t* l, const mat2_t* r);
|
||||
void mat3_add(mat3_t* x, const mat3_t* l, const mat3_t* r);
|
||||
void mat4_add(mat4_t* x, const mat4_t* l, const mat4_t* r);
|
||||
|
||||
void mat2_addeq(mat2_t* x, const mat2_t* r);
|
||||
void mat3_addeq(mat3_t* x, const mat3_t* r);
|
||||
void mat4_addeq(mat4_t* x, const mat4_t* r);
|
||||
|
||||
void mat2_sub(mat2_t* x, const mat2_t* l, const mat2_t* r);
|
||||
void mat3_sub(mat3_t* x, const mat3_t* l, const mat3_t* r);
|
||||
void mat4_sub(mat4_t* x, const mat4_t* l, const mat4_t* r);
|
||||
|
||||
void mat2_subeq(mat2_t* x, const mat2_t* r);
|
||||
void mat3_subeq(mat3_t* x, const mat3_t* r);
|
||||
void mat4_subeq(mat4_t* x, const mat4_t* r);
|
||||
|
||||
void mat2_mul(mat2_t* x, const mat2_t* l, const mat2_t* r);
|
||||
void mat3_mul(mat3_t* x, const mat3_t* l, const mat3_t* r);
|
||||
void mat4_mul(mat4_t* x, const mat4_t* l, const mat4_t* r);
|
||||
|
||||
void mat2_mul_vec2(vec2_t* x, const mat2_t* l, const vec2_t* r);
|
||||
void mat3_mul_vec3(vec3_t* x, const mat3_t* l, const vec3_t* r);
|
||||
void mat4_mul_vec4(vec4_t* x, const mat4_t* l, const vec4_t* r);
|
||||
|
||||
float mat2_det(const mat2_t* x);
|
||||
float mat3_det(const mat3_t* x);
|
||||
float mat4_det(const mat4_t* x);
|
||||
|
||||
void mat3_cofactor(mat2_t* x, const mat3_t* r, size_t row, size_t col);
|
||||
void mat4_cofactor(mat3_t* x, const mat4_t* r, size_t row, size_t col);
|
||||
|
||||
bool mat3_inv(mat3_t* x, const mat3_t* r);
|
||||
bool mat4_inv(mat4_t* x, const mat4_t* r);
|
240
util/math/matrix.sos.c
Normal file
240
util/math/matrix.sos.c
Normal file
@@ -0,0 +1,240 @@
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define SWAP(a, b) do { \
|
||||
typeof(a) macro_SWAP_temp_ = a; \
|
||||
a = b; \
|
||||
b = macro_SWAP_temp_; \
|
||||
} while (0)
|
||||
|
||||
static void next_permutation(size_t* p, size_t* c, size_t len) {
|
||||
assert(p != NULL);
|
||||
assert(c != NULL);
|
||||
|
||||
size_t i = 0;
|
||||
while (i < len) {
|
||||
if (c[i] < i) {
|
||||
if (i%2 == 0) {
|
||||
SWAP(p[0], p[i]);
|
||||
} else {
|
||||
SWAP(p[c[i]], p[i]);
|
||||
}
|
||||
++c[i];
|
||||
return;
|
||||
}
|
||||
c[i] = 0;
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
static void valid(size_t dim) {
|
||||
assert(dim >= 2);
|
||||
|
||||
printf("bool mat%zu_valid(const mat%zu_t* m) {", dim, dim);
|
||||
|
||||
printf("return m != NULL && MATH_FLOAT_VALID(m->ptr[0])");
|
||||
for (size_t i = 1; i < dim*dim; ++i) {
|
||||
printf("&& MATH_FLOAT_VALID(m->ptr[%zu])", i);
|
||||
}
|
||||
printf(";}\n");
|
||||
}
|
||||
|
||||
static void each(size_t dim, const char* name, char op) {
|
||||
assert(dim >= 2);
|
||||
|
||||
printf("void mat%zu_%s(mat%zu_t* x, const mat%zu_t* l, const mat%zu_t* r) {", dim, name, dim, dim, dim);
|
||||
|
||||
printf("assert(x != NULL);");
|
||||
printf("assert(mat%zu_valid(l));", dim);
|
||||
printf("assert(mat%zu_valid(r));", dim);
|
||||
|
||||
for (size_t i = 0; i < dim*dim; ++i) {
|
||||
printf("x->ptr[%zu] = l->ptr[%zu] %c r->ptr[%zu];", i, i, op, i);
|
||||
}
|
||||
printf("}\n");
|
||||
}
|
||||
|
||||
static void eacheq(size_t dim, const char* name, char op) {
|
||||
assert(dim >= 2);
|
||||
|
||||
printf("void mat%zu_%s(mat%zu_t* x, const mat%zu_t* r) {", dim, name, dim, dim);
|
||||
|
||||
printf("assert(mat%zu_valid(x));", dim);
|
||||
printf("assert(mat%zu_valid(r));", dim);
|
||||
|
||||
for (size_t i = 0; i < dim*dim; ++i) {
|
||||
printf("x->ptr[%zu] %c= r->ptr[%zu];", i, op, i);
|
||||
}
|
||||
printf("}\n");
|
||||
}
|
||||
|
||||
static void mul(size_t dim) {
|
||||
assert(dim >= 2);
|
||||
|
||||
printf("void mat%zu_mul(mat%zu_t* x, const mat%zu_t* l, const mat%zu_t* r) {", dim, dim, dim, dim);
|
||||
|
||||
printf("assert(x != NULL);");
|
||||
printf("assert(mat%zu_valid(l));", dim);
|
||||
printf("assert(mat%zu_valid(r));", dim);
|
||||
|
||||
for (size_t i = 0; i < dim; ++i) {
|
||||
for (size_t j = 0; j < dim; ++j) {
|
||||
printf("x->elm[%zu][%zu] = 0", j, i);
|
||||
for (size_t k = 0; k < dim; ++k) {
|
||||
printf("+ l->elm[%zu][%zu] * r->elm[%zu][%zu]", k, i, j, k);
|
||||
}
|
||||
printf(";");
|
||||
}
|
||||
}
|
||||
printf("}\n");
|
||||
}
|
||||
|
||||
static void mul_vec(size_t dim) {
|
||||
assert(dim >= 2);
|
||||
|
||||
printf("void mat%zu_mul_vec%zu(vec%zu_t* x, const mat%zu_t* l, const vec%zu_t* r) {", dim, dim, dim, dim, dim);
|
||||
|
||||
printf("assert(x != NULL);");
|
||||
printf("assert(mat%zu_valid(l));", dim);
|
||||
printf("assert(vec%zu_valid(r));", dim);
|
||||
|
||||
for (size_t i = 0; i < dim; ++i) {
|
||||
printf("x->ptr[%zu] = 0", i);
|
||||
for (size_t j = 0; j < dim; ++j) {
|
||||
printf("+ l->elm[%zu][%zu] * r->ptr[%zu]", j, i, j);
|
||||
}
|
||||
printf(";");
|
||||
}
|
||||
printf("}\n");
|
||||
}
|
||||
|
||||
static void det(size_t dim) {
|
||||
assert(dim >= 2);
|
||||
|
||||
size_t p[dim];
|
||||
for (size_t i = 0; i < dim; ++i) p[i] = i;
|
||||
|
||||
size_t c[dim];
|
||||
for (size_t i = 0; i < dim; ++i) c[i] = 0;
|
||||
|
||||
printf("float mat%zu_det(const mat%zu_t* x) {", dim, dim);
|
||||
printf("assert(mat%zu_valid(x));", dim);
|
||||
|
||||
size_t f = 1;
|
||||
for (size_t i = 1; i <= dim; ++i) f *= i;
|
||||
|
||||
printf("return 0");
|
||||
|
||||
bool positive = true;
|
||||
for (size_t i = 0; i < f; ++i) {
|
||||
printf("%c1", positive? '+': '-');
|
||||
for (size_t j = 0; j < dim; ++j) {
|
||||
printf("*x->elm[%zu][%zu]", p[j], j);
|
||||
}
|
||||
next_permutation(p, c, dim);
|
||||
positive = !positive;
|
||||
}
|
||||
printf(";}\n");
|
||||
}
|
||||
|
||||
static void cofactor(size_t dim) {
|
||||
assert(dim >= 3);
|
||||
|
||||
printf("void mat%zu_cofactor(mat%zu_t* x, const mat%zu_t* r, size_t row, size_t col) {", dim, dim-1, dim);
|
||||
|
||||
printf("assert(x != NULL);");
|
||||
printf("assert(mat%zu_valid(r));", dim);
|
||||
printf("assert(row < %zu);", dim);
|
||||
printf("assert(col < %zu);", dim);
|
||||
|
||||
for (size_t row = 0; row < dim; ++row) {
|
||||
for (size_t col = 0; col < dim; ++col) {
|
||||
printf("if (row == %zu && col == %zu) {", row, col);
|
||||
for (size_t xi = 0; xi < dim-1; ++xi) {
|
||||
const size_t ri = xi >= row? xi+1: xi;
|
||||
for (size_t xj = 0; xj < dim-1; ++xj) {
|
||||
const size_t rj = xj >= col? xj+1: xj;
|
||||
printf("x->elm[%zu][%zu] = %s r->elm[%zu][%zu];", xj, xi, (xj+xi)%2 == 0? "": "-", rj, ri);
|
||||
}
|
||||
}
|
||||
printf("return;}");
|
||||
}
|
||||
}
|
||||
printf("}\n");
|
||||
}
|
||||
|
||||
static void inv(size_t dim) {
|
||||
assert(dim >= 3);
|
||||
|
||||
printf("bool mat%zu_inv(mat%zu_t* x, const mat%zu_t* r) {", dim, dim, dim);
|
||||
|
||||
printf("assert(x != NULL);");
|
||||
printf("assert(mat%zu_valid(r));", dim);
|
||||
|
||||
printf("const float d = mat%zu_det(r);", dim);
|
||||
printf("if (d == 0) return false;");
|
||||
|
||||
printf("mat%zu_t co;", dim-1);
|
||||
for (size_t i = 0; i < dim; ++i) {
|
||||
for (size_t j = 0; j < dim; ++j) {
|
||||
printf("mat%zu_cofactor(&co, r, %zu, %zu);", dim, i, j);
|
||||
printf("x->elm[%zu][%zu] = mat%zu_det(&co) / d;", j, i, dim-1);
|
||||
}
|
||||
}
|
||||
printf("return true;");
|
||||
printf("}\n");
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
(void) argc, (void) argv;
|
||||
|
||||
printf("#include \"./matrix.h\"\n");
|
||||
|
||||
printf("#include <assert.h>\n");
|
||||
printf("#include <stdbool.h>\n");
|
||||
printf("#include <stddef.h>\n");
|
||||
|
||||
printf("#include \"./algorithm.h\"\n");
|
||||
printf("#include \"./vector.h\"\n");
|
||||
|
||||
valid(2);
|
||||
valid(3);
|
||||
valid(4);
|
||||
|
||||
each(2, "add", '+');
|
||||
each(3, "add", '+');
|
||||
each(4, "add", '+');
|
||||
|
||||
eacheq(2, "addeq", '+');
|
||||
eacheq(3, "addeq", '+');
|
||||
eacheq(4, "addeq", '+');
|
||||
|
||||
each(2, "sub", '-');
|
||||
each(3, "sub", '-');
|
||||
each(4, "sub", '-');
|
||||
|
||||
eacheq(2, "subeq", '-');
|
||||
eacheq(3, "subeq", '-');
|
||||
eacheq(4, "subeq", '-');
|
||||
|
||||
mul(2);
|
||||
mul(3);
|
||||
mul(4);
|
||||
|
||||
mul_vec(2);
|
||||
mul_vec(3);
|
||||
mul_vec(4);
|
||||
|
||||
det(2);
|
||||
det(3);
|
||||
det(4);
|
||||
|
||||
cofactor(3);
|
||||
cofactor(4);
|
||||
|
||||
inv(3);
|
||||
inv(4);
|
||||
}
|
110
util/math/rational.c
Normal file
110
util/math/rational.c
Normal file
@@ -0,0 +1,110 @@
|
||||
#include "./rational.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include "./algorithm.h"
|
||||
|
||||
bool rational_valid(const rational_t* x) {
|
||||
return x != NULL && x->den != 0;
|
||||
}
|
||||
|
||||
void rational_add(rational_t* x, const rational_t* a, const rational_t* b) {
|
||||
assert(x != NULL);
|
||||
assert(rational_valid(a));
|
||||
assert(rational_valid(b));
|
||||
|
||||
const int64_t d = math_int64_lcm(a->den, b->den);
|
||||
|
||||
*x = (typeof(*x)) {
|
||||
.num = a->num*(d/a->den) + b->num*(d/b->den),
|
||||
.den = d,
|
||||
};
|
||||
}
|
||||
void rational_addeq(rational_t* x, const rational_t* a) {
|
||||
assert(rational_valid(x));
|
||||
assert(rational_valid(a));
|
||||
|
||||
const int64_t d = math_int64_lcm(x->den, a->den);
|
||||
x->num = x->num*(d/x->den) + a->num*(d/a->den);
|
||||
x->den = d;
|
||||
}
|
||||
|
||||
void rational_sub(rational_t* x, const rational_t* a, const rational_t* b) {
|
||||
assert(x != NULL);
|
||||
assert(rational_valid(a));
|
||||
assert(rational_valid(b));
|
||||
|
||||
rational_t c = *b;
|
||||
c.num *= -1;
|
||||
rational_add(x, a, &c);
|
||||
}
|
||||
void rational_subeq(rational_t* x, const rational_t* a) {
|
||||
assert(rational_valid(x));
|
||||
assert(rational_valid(a));
|
||||
|
||||
rational_t c = *a;
|
||||
c.num *= -1;
|
||||
rational_addeq(x, &c);
|
||||
}
|
||||
|
||||
void rational_mul(rational_t* x, const rational_t* a, const rational_t* b) {
|
||||
assert(x != NULL);
|
||||
assert(rational_valid(a));
|
||||
assert(rational_valid(b));
|
||||
|
||||
*x = (typeof(*x)) {
|
||||
.num = a->num*b->num,
|
||||
.den = a->den*b->den,
|
||||
};
|
||||
}
|
||||
void rational_muleq(rational_t* x, const rational_t* a) {
|
||||
assert(rational_valid(x));
|
||||
assert(rational_valid(a));
|
||||
|
||||
x->num *= a->num;
|
||||
x->den *= a->den;
|
||||
}
|
||||
|
||||
void rational_div(rational_t* x, const rational_t* a, const rational_t* b) {
|
||||
assert(x != NULL);
|
||||
assert(rational_valid(a));
|
||||
assert(rational_valid(b));
|
||||
assert(b->num != 0);
|
||||
|
||||
*x = (typeof(*x)) {
|
||||
.num = a->num*b->den,
|
||||
.den = a->den*b->num,
|
||||
};
|
||||
}
|
||||
void rational_diveq(rational_t* x, const rational_t* a) {
|
||||
assert(rational_valid(x));
|
||||
assert(rational_valid(a));
|
||||
assert(a->num != 0);
|
||||
|
||||
x->num *= a->den;
|
||||
x->den *= a->num;
|
||||
}
|
||||
|
||||
void rational_simplify(rational_t* x) {
|
||||
assert(rational_valid(x));
|
||||
|
||||
const int64_t d =
|
||||
x->num != 0? math_int64_gcd(MATH_ABS(x->num), MATH_ABS(x->den)): x->den;
|
||||
|
||||
x->num /= d;
|
||||
x->den /= d;
|
||||
}
|
||||
void rational_normalize(rational_t* x, int64_t den) {
|
||||
assert(rational_valid(x));
|
||||
assert(den != 0);
|
||||
|
||||
x->num = x->num * den / x->den;
|
||||
x->den = den;
|
||||
}
|
||||
|
||||
float rational_calculate(const rational_t* x) {
|
||||
assert(rational_valid(x));
|
||||
|
||||
return x->num*1.0f / x->den;
|
||||
}
|
84
util/math/rational.h
Normal file
84
util/math/rational.h
Normal file
@@ -0,0 +1,84 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct {
|
||||
int64_t num;
|
||||
int64_t den;
|
||||
} rational_t;
|
||||
|
||||
#define rational(n, d) ((rational_t) { \
|
||||
.num = n, \
|
||||
.den = d, \
|
||||
})
|
||||
|
||||
bool
|
||||
rational_valid(
|
||||
const rational_t* x
|
||||
);
|
||||
|
||||
void
|
||||
rational_add(
|
||||
rational_t* x,
|
||||
const rational_t* a,
|
||||
const rational_t* b
|
||||
);
|
||||
void
|
||||
rational_addeq(
|
||||
rational_t* x,
|
||||
const rational_t* a
|
||||
);
|
||||
|
||||
void
|
||||
rational_sub(
|
||||
rational_t* x,
|
||||
const rational_t* a,
|
||||
const rational_t* b
|
||||
);
|
||||
void
|
||||
rational_subeq(
|
||||
rational_t* x,
|
||||
const rational_t* a
|
||||
);
|
||||
|
||||
void
|
||||
rational_mul(
|
||||
rational_t* x,
|
||||
const rational_t* a,
|
||||
const rational_t* b
|
||||
);
|
||||
void
|
||||
rational_muleq(
|
||||
rational_t* x,
|
||||
const rational_t* a
|
||||
);
|
||||
|
||||
void
|
||||
rational_div(
|
||||
rational_t* x,
|
||||
const rational_t* a,
|
||||
const rational_t* b
|
||||
);
|
||||
void
|
||||
rational_diveq(
|
||||
rational_t* x,
|
||||
const rational_t* a
|
||||
);
|
||||
|
||||
void
|
||||
rational_simplify(
|
||||
rational_t* x
|
||||
);
|
||||
|
||||
/* Don't forget that this operation may cause a discrepancy. */
|
||||
void
|
||||
rational_normalize(
|
||||
rational_t* x,
|
||||
int64_t den
|
||||
);
|
||||
|
||||
float
|
||||
rational_calculate(
|
||||
const rational_t* x
|
||||
);
|
111
util/math/test.c
Normal file
111
util/math/test.c
Normal file
@@ -0,0 +1,111 @@
|
||||
#undef NDEBUG
|
||||
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
#include <float.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "./algorithm.h"
|
||||
#include "./matrix.h"
|
||||
#include "./rational.h"
|
||||
#include "./vector.h"
|
||||
|
||||
static bool vec4_is_equal(vec4_t v1, vec4_t v2) {
|
||||
return
|
||||
MATH_FLOAT_EQUAL(v1.x, v2.x) &&
|
||||
MATH_FLOAT_EQUAL(v1.y, v2.y) &&
|
||||
MATH_FLOAT_EQUAL(v1.z, v2.z) &&
|
||||
MATH_FLOAT_EQUAL(v1.w, v2.w);
|
||||
}
|
||||
|
||||
static void test_matrix() {
|
||||
vec4_t v1, v2;
|
||||
mat4_t m1, m2, m3;
|
||||
|
||||
v1 = vec4(1, 1, 1, 1);
|
||||
m1 = mat4_identity();
|
||||
mat4_mul_vec4(&v2, &m1, &v1);
|
||||
assert(vec4_is_equal(v2, v1));
|
||||
|
||||
v1 = vec4(1, 1, 1, 1);
|
||||
m1 = mat4_translation(1, 1, 1);
|
||||
mat4_mul_vec4(&v2, &m1, &v1);
|
||||
assert(vec4_is_equal(v2, vec4(2, 2, 2, 1)));
|
||||
|
||||
v1 = vec4(1, 1, 1, 1);
|
||||
m1 = mat4_scale(.5f, 2, 1);
|
||||
mat4_mul_vec4(&v2, &m1, &v1);
|
||||
assert(vec4_is_equal(v2, vec4(.5f, 2, 1, 1)));
|
||||
|
||||
v1 = vec4(0, 1, 0, 1);
|
||||
m1 = mat4_rotation_x(-1 /* = sin -PI/2 */, 0 /* = cos -PI/2 */);
|
||||
mat4_mul_vec4(&v2, &m1, &v1);
|
||||
assert(vec4_is_equal(v2, vec4(0, 0, -1, 1)));
|
||||
|
||||
v1 = vec4(1, 0, 0, 1);
|
||||
m1 = mat4_rotation_y(1 /* = sin PI/2 */, 0 /* = cos PI/2 */);
|
||||
mat4_mul_vec4(&v2, &m1, &v1);
|
||||
assert(vec4_is_equal(v2, vec4(0, 0, -1, 1)));
|
||||
|
||||
v1 = vec4(0, 0, -1, 1);
|
||||
m1 = mat4_rotation_z(1 /* = sin PI/2 */, 0 /* = cos PI/2 */);
|
||||
mat4_mul_vec4(&v2, &m1, &v1);
|
||||
assert(vec4_is_equal(v2, vec4(0, 0, -1, 1)));
|
||||
|
||||
v1 = vec4(-1, 1, 0, 1);
|
||||
m1 = mat4_rotation_z(1 /* = sin PI/2 */, 0 /* = cos PI/2 */);
|
||||
m2 = mat4_scale(.5f, .5f, .5f);
|
||||
mat4_mul(&m3, &m2, &m1);
|
||||
m1 = mat4_translation(1, 1, 0);
|
||||
mat4_mul(&m2, &m1, &m3);
|
||||
mat4_mul_vec4(&v2, &m2, &v1);
|
||||
assert(vec4_is_equal(v2, vec4(.5f, .5f, 0, 1)));
|
||||
}
|
||||
|
||||
static void test_algorithm(void) {
|
||||
assert(math_int32_next_power2(8) == 8);
|
||||
assert(math_int32_next_power2(20) == 32);
|
||||
assert(math_int32_next_power2(200) == 256);
|
||||
|
||||
assert(math_int32_gcd(10, 5) == 5);
|
||||
assert(math_int32_gcd(12, 6) == 6);
|
||||
assert(math_int32_gcd(12, 9) == 3);
|
||||
assert(math_int32_gcd(12, 11) == 1);
|
||||
|
||||
assert(math_int32_lcm(10, 5) == 10);
|
||||
assert(math_int32_lcm(12, 10) == 60);
|
||||
assert(math_int32_lcm(7, 8) == 56);
|
||||
}
|
||||
|
||||
static void test_rational(void) {
|
||||
rational_t x, y;
|
||||
|
||||
x = rational(1, 2);
|
||||
y = rational(1, 4);
|
||||
rational_addeq(&x, &y);
|
||||
assert(x.num == 3 && x.den == 4);
|
||||
|
||||
x = rational(1, 2);
|
||||
y = rational(1, 3);
|
||||
rational_muleq(&x, &y);
|
||||
assert(x.num == 1 && x.den == 6);
|
||||
|
||||
x = rational(10, 20);
|
||||
rational_simplify(&x);
|
||||
assert(x.num == 1 && x.den == 2);
|
||||
|
||||
x = rational(1, 2);
|
||||
rational_normalize(&x, 100);
|
||||
assert(x.num == 50 && x.den == 100);
|
||||
|
||||
x = rational(1, 2);
|
||||
assert(rational_calculate(&x) == 1.0f/2.0f);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
test_matrix();
|
||||
test_algorithm();
|
||||
test_rational();
|
||||
return 0;
|
||||
}
|
275
util/math/vector.c
Normal file
275
util/math/vector.c
Normal file
@@ -0,0 +1,275 @@
|
||||
#include "./vector.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include "./algorithm.h"
|
||||
|
||||
bool vec2_valid(const vec2_t* x) {
|
||||
return x != NULL &&
|
||||
MATH_FLOAT_VALID(x->ptr[0]) &&
|
||||
MATH_FLOAT_VALID(x->ptr[1]);
|
||||
}
|
||||
bool vec3_valid(const vec3_t* x) {
|
||||
return x != NULL &&
|
||||
MATH_FLOAT_VALID(x->ptr[0]) &&
|
||||
MATH_FLOAT_VALID(x->ptr[1]) &&
|
||||
MATH_FLOAT_VALID(x->ptr[2]);
|
||||
}
|
||||
bool vec4_valid(const vec4_t* x) {
|
||||
return x != NULL &&
|
||||
MATH_FLOAT_VALID(x->ptr[0]) &&
|
||||
MATH_FLOAT_VALID(x->ptr[1]) &&
|
||||
MATH_FLOAT_VALID(x->ptr[2]) &&
|
||||
MATH_FLOAT_VALID(x->ptr[3]);
|
||||
}
|
||||
|
||||
void vec2_add(vec2_t* x, const vec2_t* l, const vec2_t* r) {
|
||||
assert(x != NULL);
|
||||
assert(l != NULL);
|
||||
assert(r != NULL);
|
||||
|
||||
x->x = l->x + r->x;
|
||||
x->y = l->y + r->y;
|
||||
}
|
||||
void vec3_add(vec3_t* x, const vec3_t* l, const vec3_t* r) {
|
||||
assert(x != NULL);
|
||||
assert(l != NULL);
|
||||
assert(r != NULL);
|
||||
|
||||
x->x = l->x + r->x;
|
||||
x->y = l->y + r->y;
|
||||
x->z = l->z + r->z;
|
||||
}
|
||||
void vec4_add(vec4_t* x, const vec4_t* l, const vec4_t* r) {
|
||||
assert(x != NULL);
|
||||
assert(l != NULL);
|
||||
assert(r != NULL);
|
||||
|
||||
x->x = l->x + r->x;
|
||||
x->y = l->y + r->y;
|
||||
x->z = l->z + r->z;
|
||||
x->w = l->w + r->w;
|
||||
}
|
||||
|
||||
void vec2_addeq(vec2_t* x, const vec2_t* r) {
|
||||
assert(x != NULL);
|
||||
assert(r != NULL);
|
||||
|
||||
x->x += r->x;
|
||||
x->y += r->y;
|
||||
}
|
||||
void vec3_addeq(vec3_t* x, const vec3_t* r) {
|
||||
assert(x != NULL);
|
||||
assert(r != NULL);
|
||||
|
||||
x->x += r->x;
|
||||
x->y += r->y;
|
||||
x->z += r->z;
|
||||
}
|
||||
void vec4_addeq(vec4_t* x, const vec4_t* r) {
|
||||
assert(x != NULL);
|
||||
assert(r != NULL);
|
||||
|
||||
x->x += r->x;
|
||||
x->y += r->y;
|
||||
x->z += r->z;
|
||||
x->w += r->w;
|
||||
}
|
||||
|
||||
void vec2_sub(vec2_t* x, const vec2_t* l, const vec2_t* r) {
|
||||
assert(x != NULL);
|
||||
assert(l != NULL);
|
||||
assert(r != NULL);
|
||||
|
||||
x->x = l->x - r->x;
|
||||
x->y = l->y - r->y;
|
||||
}
|
||||
void vec3_sub(vec3_t* x, const vec3_t* l, const vec3_t* r) {
|
||||
assert(x != NULL);
|
||||
assert(l != NULL);
|
||||
assert(r != NULL);
|
||||
|
||||
x->x = l->x - r->x;
|
||||
x->y = l->y - r->y;
|
||||
x->z = l->z - r->z;
|
||||
}
|
||||
void vec4_sub(vec4_t* x, const vec4_t* l, const vec4_t* r) {
|
||||
assert(x != NULL);
|
||||
assert(l != NULL);
|
||||
assert(r != NULL);
|
||||
|
||||
x->x = l->x - r->x;
|
||||
x->y = l->y - r->y;
|
||||
x->z = l->z - r->z;
|
||||
x->w = l->w - r->w;
|
||||
}
|
||||
|
||||
void vec2_subeq(vec2_t* x, const vec2_t* r) {
|
||||
assert(x != NULL);
|
||||
assert(r != NULL);
|
||||
|
||||
x->x -= r->x;
|
||||
x->y -= r->y;
|
||||
}
|
||||
void vec3_subeq(vec3_t* x, const vec3_t* r) {
|
||||
assert(x != NULL);
|
||||
assert(r != NULL);
|
||||
|
||||
x->x -= r->x;
|
||||
x->y -= r->y;
|
||||
x->z -= r->z;
|
||||
}
|
||||
void vec4_subeq(vec4_t* x, const vec4_t* r) {
|
||||
assert(x != NULL);
|
||||
assert(r != NULL);
|
||||
|
||||
x->x -= r->x;
|
||||
x->y -= r->y;
|
||||
x->z -= r->z;
|
||||
x->w -= r->w;
|
||||
}
|
||||
|
||||
void vec2_mul(vec2_t* x, const vec2_t* l, float r) {
|
||||
assert(x != NULL);
|
||||
assert(l != NULL);
|
||||
|
||||
x->x = l->x * r;
|
||||
x->y = l->y * r;
|
||||
}
|
||||
void vec3_mul(vec3_t* x, const vec3_t* l, float r) {
|
||||
assert(x != NULL);
|
||||
assert(l != NULL);
|
||||
|
||||
x->x = l->x * r;
|
||||
x->y = l->y * r;
|
||||
x->z = l->z * r;
|
||||
}
|
||||
void vec4_mul(vec4_t* x, const vec4_t* l, float r) {
|
||||
assert(x != NULL);
|
||||
assert(l != NULL);
|
||||
|
||||
x->x = l->x * r;
|
||||
x->y = l->y * r;
|
||||
x->z = l->z * r;
|
||||
x->w = l->w * r;
|
||||
}
|
||||
|
||||
void vec2_muleq(vec2_t* x, float r) {
|
||||
assert(x != NULL);
|
||||
|
||||
x->x *= r;
|
||||
x->y *= r;
|
||||
}
|
||||
void vec3_muleq(vec3_t* x, float r) {
|
||||
assert(x != NULL);
|
||||
|
||||
x->x *= r;
|
||||
x->y *= r;
|
||||
x->z *= r;
|
||||
}
|
||||
void vec4_muleq(vec4_t* x, float r) {
|
||||
assert(x != NULL);
|
||||
|
||||
x->x *= r;
|
||||
x->y *= r;
|
||||
x->z *= r;
|
||||
x->w *= r;
|
||||
}
|
||||
|
||||
void vec2_div(vec2_t* x, const vec2_t* l, float r) {
|
||||
assert(x != NULL);
|
||||
assert(l != NULL);
|
||||
|
||||
x->x = l->x / r;
|
||||
x->y = l->y / r;
|
||||
}
|
||||
void vec3_div(vec3_t* x, const vec3_t* l, float r) {
|
||||
assert(x != NULL);
|
||||
assert(l != NULL);
|
||||
|
||||
x->x = l->x / r;
|
||||
x->y = l->y / r;
|
||||
x->z = l->z / r;
|
||||
}
|
||||
void vec4_div(vec4_t* x, const vec4_t* l, float r) {
|
||||
assert(x != NULL);
|
||||
assert(l != NULL);
|
||||
|
||||
x->x = l->x / r;
|
||||
x->y = l->y / r;
|
||||
x->z = l->z / r;
|
||||
x->w = l->w / r;
|
||||
}
|
||||
|
||||
void vec2_diveq(vec2_t* x, float r) {
|
||||
assert(x != NULL);
|
||||
|
||||
x->x /= r;
|
||||
x->y /= r;
|
||||
}
|
||||
void vec3_diveq(vec3_t* x, float r) {
|
||||
assert(x != NULL);
|
||||
|
||||
x->x /= r;
|
||||
x->y /= r;
|
||||
x->z /= r;
|
||||
}
|
||||
void vec4_diveq(vec4_t* x, float r) {
|
||||
assert(x != NULL);
|
||||
|
||||
x->x /= r;
|
||||
x->y /= r;
|
||||
x->z /= r;
|
||||
x->w /= r;
|
||||
}
|
||||
|
||||
float vec2_pow_length(const vec2_t* x) {
|
||||
assert(x != NULL);
|
||||
return x->x*x->x + x->y*x->y;
|
||||
}
|
||||
float vec3_pow_length(const vec3_t* x) {
|
||||
assert(x != NULL);
|
||||
return x->x*x->x + x->y*x->y + x->z*x->z;
|
||||
}
|
||||
float vec4_pow_length(const vec4_t* x) {
|
||||
assert(x != NULL);
|
||||
return x->x*x->x + x->y*x->y + x->z*x->z + x->w*x->w;
|
||||
}
|
||||
|
||||
float vec2_length(const vec2_t* x) {
|
||||
assert(x != NULL);
|
||||
return sqrtf(vec2_pow_length(x));
|
||||
}
|
||||
float vec3_length(const vec3_t* x) {
|
||||
assert(x != NULL);
|
||||
return sqrtf(vec3_pow_length(x));
|
||||
}
|
||||
float vec4_length(const vec4_t* x) {
|
||||
assert(x != NULL);
|
||||
return sqrtf(vec4_pow_length(x));
|
||||
}
|
||||
|
||||
float vec2_dot(const vec2_t* l, const vec2_t* r) {
|
||||
assert(l != NULL);
|
||||
assert(r != NULL);
|
||||
return l->x*r->x + l->y*r->y;
|
||||
}
|
||||
float vec3_dot(const vec3_t* l, const vec3_t* r) {
|
||||
assert(l != NULL);
|
||||
assert(r != NULL);
|
||||
return l->x*r->x + l->y*r->y + l->z*r->z;
|
||||
}
|
||||
float vec4_dot(const vec4_t* l, const vec4_t* r) {
|
||||
assert(l != NULL);
|
||||
assert(r != NULL);
|
||||
return l->x*r->x + l->y*r->y + l->z*r->z + l->w*r->w;
|
||||
}
|
||||
|
||||
float vec2_cross(const vec2_t* l, const vec2_t* r) {
|
||||
assert(l != NULL);
|
||||
assert(r != NULL);
|
||||
return l->x*r->y - l->y*r->x;
|
||||
}
|
78
util/math/vector.h
Normal file
78
util/math/vector.h
Normal file
@@ -0,0 +1,78 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef union {
|
||||
struct { float x, y; };
|
||||
float ptr[2];
|
||||
} vec2_t;
|
||||
|
||||
typedef union {
|
||||
struct { float x, y, z; };
|
||||
float ptr[3];
|
||||
|
||||
vec2_t xy;
|
||||
} vec3_t;
|
||||
|
||||
typedef union {
|
||||
struct { float x, y, z, w; };
|
||||
float ptr[4];
|
||||
|
||||
vec2_t xy;
|
||||
vec3_t xyz;
|
||||
} vec4_t;
|
||||
|
||||
#define vec2(x, y) (vec2_t) {{x, y}}
|
||||
#define vec3(x, y, z) (vec3_t) {{x, y, z}}
|
||||
#define vec4(x, y, z, w) (vec4_t) {{x, y, z, w}}
|
||||
|
||||
bool vec2_valid(const vec2_t* x);
|
||||
bool vec3_valid(const vec3_t* x);
|
||||
bool vec4_valid(const vec4_t* x);
|
||||
|
||||
void vec2_add(vec2_t* x, const vec2_t* l, const vec2_t* r);
|
||||
void vec3_add(vec3_t* x, const vec3_t* l, const vec3_t* r);
|
||||
void vec4_add(vec4_t* x, const vec4_t* l, const vec4_t* r);
|
||||
|
||||
void vec2_addeq(vec2_t* x, const vec2_t* r);
|
||||
void vec3_addeq(vec3_t* x, const vec3_t* r);
|
||||
void vec4_addeq(vec4_t* x, const vec4_t* r);
|
||||
|
||||
void vec2_sub(vec2_t* x, const vec2_t* l, const vec2_t* r);
|
||||
void vec3_sub(vec3_t* x, const vec3_t* l, const vec3_t* r);
|
||||
void vec4_sub(vec4_t* x, const vec4_t* l, const vec4_t* r);
|
||||
|
||||
void vec2_subeq(vec2_t* x, const vec2_t* r);
|
||||
void vec3_subeq(vec3_t* x, const vec3_t* r);
|
||||
void vec4_subeq(vec4_t* x, const vec4_t* r);
|
||||
|
||||
void vec2_mul(vec2_t* x, const vec2_t* l, float r);
|
||||
void vec3_mul(vec3_t* x, const vec3_t* l, float r);
|
||||
void vec4_mul(vec4_t* x, const vec4_t* l, float r);
|
||||
|
||||
void vec2_muleq(vec2_t* x, float r);
|
||||
void vec3_muleq(vec3_t* x, float r);
|
||||
void vec4_muleq(vec4_t* x, float r);
|
||||
|
||||
void vec2_div(vec2_t* x, const vec2_t* l, float r);
|
||||
void vec3_div(vec3_t* x, const vec3_t* l, float r);
|
||||
void vec4_div(vec4_t* x, const vec4_t* l, float r);
|
||||
|
||||
void vec2_diveq(vec2_t* x, float r);
|
||||
void vec3_diveq(vec3_t* x, float r);
|
||||
void vec4_diveq(vec4_t* x, float r);
|
||||
|
||||
float vec2_pow_length(const vec2_t* x);
|
||||
float vec3_pow_length(const vec3_t* x);
|
||||
float vec4_pow_length(const vec4_t* x);
|
||||
|
||||
float vec2_length(const vec2_t* x);
|
||||
float vec3_length(const vec3_t* x);
|
||||
float vec4_length(const vec4_t* x);
|
||||
|
||||
float vec2_dot(const vec2_t* l, const vec2_t* r);
|
||||
float vec3_dot(const vec3_t* l, const vec3_t* r);
|
||||
float vec4_dot(const vec4_t* l, const vec4_t* r);
|
||||
|
||||
float vec2_cross(const vec2_t* l, const vec2_t* r);
|
||||
/* TODO(catfoot): add vec3_cross function */
|
10
util/memory/CMakeLists.txt
Normal file
10
util/memory/CMakeLists.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
add_library(memory
|
||||
memory.c
|
||||
)
|
||||
|
||||
if (BUILD_TESTING)
|
||||
add_executable(memory-test test.c)
|
||||
target_link_libraries(memory-test memory)
|
||||
|
||||
add_test(memory-test memory-test)
|
||||
endif()
|
81
util/memory/memory.c
Normal file
81
util/memory/memory.c
Normal file
@@ -0,0 +1,81 @@
|
||||
#include "./memory.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdarg.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef __linux__
|
||||
# include <execinfo.h>
|
||||
#endif
|
||||
|
||||
#define MEMORY_LOG_FILE "memory-trace"
|
||||
|
||||
void* memory_new(size_t size) {
|
||||
if (size == 0) return NULL;
|
||||
|
||||
void* ptr = malloc(size);
|
||||
if (ptr == NULL) {
|
||||
fprintf(stderr, "failed to allocate %zu bytes memory", size);
|
||||
abort();
|
||||
}
|
||||
|
||||
memory_log("new 0x%tx (%zu bytes)", (ptrdiff_t) ptr, size);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void* memory_resize(void* ptr, size_t size) {
|
||||
if (ptr == NULL) return memory_new(size);
|
||||
if (size == 0) return ptr;
|
||||
|
||||
void* newptr = realloc(ptr, size);
|
||||
if (newptr == NULL) {
|
||||
fprintf(stderr,
|
||||
"failed to allocate %zu bytes memory for resizing 0x%tx",
|
||||
size, (ptrdiff_t) ptr);
|
||||
abort();
|
||||
}
|
||||
|
||||
memory_log("resize 0x%tx -> 0x%tx (%zu bytes)",
|
||||
(ptrdiff_t) ptr, (ptrdiff_t) newptr, size);
|
||||
return newptr;
|
||||
}
|
||||
|
||||
void memory_delete(void* ptr) {
|
||||
if (ptr == NULL) return;
|
||||
free(ptr);
|
||||
|
||||
memory_log("delete 0x%tx", (ptrdiff_t) ptr);
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
void memory_log(const char* fmt, ...) {
|
||||
static FILE* fp = NULL;
|
||||
|
||||
if (fp == NULL) {
|
||||
fp = fopen(MEMORY_LOG_FILE, "w");
|
||||
assert(fp != NULL);
|
||||
}
|
||||
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vfprintf(fp, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
fprintf(fp, "\n");
|
||||
|
||||
# if __linux__
|
||||
void* callstack[32];
|
||||
const size_t frames = backtrace(callstack, sizeof(callstack));
|
||||
char** symbols = backtrace_symbols(callstack, frames);
|
||||
for (size_t i = 0; i < frames; ++i) {
|
||||
fprintf(fp, " %s\n", symbols[i]);
|
||||
}
|
||||
free(symbols);
|
||||
# endif /* __linux__ */
|
||||
|
||||
fprintf(fp, "\n");
|
||||
fflush(fp);
|
||||
}
|
||||
#endif /* NDEBUG */
|
29
util/memory/memory.h
Normal file
29
util/memory/memory.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
void*
|
||||
memory_new(
|
||||
size_t size
|
||||
);
|
||||
|
||||
void*
|
||||
memory_resize(
|
||||
void* ptr,
|
||||
size_t size
|
||||
);
|
||||
|
||||
void
|
||||
memory_delete(
|
||||
void* ptr
|
||||
);
|
||||
|
||||
#ifndef NDEBUG
|
||||
void
|
||||
memory_log(
|
||||
const char* fmt,
|
||||
...
|
||||
);
|
||||
#else
|
||||
# define memory_log(fmt, ...) (void) fmt
|
||||
#endif /* NDEBUG */
|
8
util/memory/test.c
Normal file
8
util/memory/test.c
Normal file
@@ -0,0 +1,8 @@
|
||||
#include "./memory.h"
|
||||
|
||||
int main(void) {
|
||||
void* ptr = memory_new(0x1);
|
||||
ptr = memory_resize(ptr, 0x2);
|
||||
memory_delete(ptr);
|
||||
return 0;
|
||||
}
|
10
util/mpkutil/CMakeLists.txt
Normal file
10
util/mpkutil/CMakeLists.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
add_library(mpkutil
|
||||
file.c
|
||||
get.c
|
||||
pack.c
|
||||
)
|
||||
target_link_libraries(mpkutil
|
||||
msgpackc
|
||||
|
||||
math
|
||||
)
|
4
util/mpkutil/README.md
Normal file
4
util/mpkutil/README.md
Normal file
@@ -0,0 +1,4 @@
|
||||
mpkutil
|
||||
====
|
||||
|
||||
utility library for msgpack-c
|
58
util/mpkutil/file.c
Normal file
58
util/mpkutil/file.c
Normal file
@@ -0,0 +1,58 @@
|
||||
#include "./file.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <msgpack.h>
|
||||
|
||||
bool mpkutil_file_unpack_with_unpacker(
|
||||
msgpack_unpacked* obj,
|
||||
FILE* fp,
|
||||
msgpack_unpacker* unpacker) {
|
||||
assert(obj != NULL);
|
||||
assert(fp != NULL);
|
||||
assert(unpacker != NULL);
|
||||
|
||||
for (;;) {
|
||||
if (feof(fp)) return false;
|
||||
|
||||
const size_t maxlen = msgpack_unpacker_buffer_capacity(unpacker);
|
||||
if (maxlen == 0) {
|
||||
fprintf(stderr, "unpacker buffer overflow\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
const size_t len = fread(
|
||||
msgpack_unpacker_buffer(unpacker), 1, maxlen, fp);
|
||||
if (ferror(fp)) return false;
|
||||
|
||||
msgpack_unpacker_buffer_consumed(unpacker, len);
|
||||
|
||||
size_t parsed_len;
|
||||
switch (msgpack_unpacker_next_with_size(unpacker, obj, &parsed_len)) {
|
||||
case MSGPACK_UNPACK_SUCCESS:
|
||||
return fseek(fp, (long) parsed_len - len, SEEK_CUR) == 0;
|
||||
case MSGPACK_UNPACK_CONTINUE:
|
||||
break;
|
||||
|
||||
case MSGPACK_UNPACK_PARSE_ERROR:
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool mpkutil_file_unpack(msgpack_unpacked* obj, FILE* fp) {
|
||||
assert(fp != NULL);
|
||||
assert(obj != NULL);
|
||||
|
||||
msgpack_unpacker unpacker;
|
||||
if (!msgpack_unpacker_init(&unpacker, 1024)) return false;
|
||||
|
||||
const bool result = mpkutil_file_unpack_with_unpacker(obj, fp, &unpacker);
|
||||
msgpack_unpacker_destroy(&unpacker);
|
||||
return result;
|
||||
}
|
19
util/mpkutil/file.h
Normal file
19
util/mpkutil/file.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <msgpack.h>
|
||||
|
||||
bool
|
||||
mpkutil_file_unpack_with_unpacker(
|
||||
msgpack_unpacked* obj,
|
||||
FILE* fp,
|
||||
msgpack_unpacker* unpacker
|
||||
);
|
||||
|
||||
bool
|
||||
mpkutil_file_unpack(
|
||||
msgpack_unpacked* obj,
|
||||
FILE* fp
|
||||
);
|
143
util/mpkutil/get.c
Normal file
143
util/mpkutil/get.c
Normal file
@@ -0,0 +1,143 @@
|
||||
#include "./get.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <msgpack.h>
|
||||
|
||||
#include "util/math/algorithm.h"
|
||||
#include "util/math/vector.h"
|
||||
|
||||
bool mpkutil_get_bool(const msgpack_object* obj, bool* b) {
|
||||
if (obj == NULL) return false;
|
||||
if (obj->type != MSGPACK_OBJECT_BOOLEAN) return false;
|
||||
if (b != NULL) *b = obj->via.boolean;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mpkutil_get_int(const msgpack_object* obj, intmax_t* i) {
|
||||
if (obj == NULL) return false;
|
||||
if (obj->type != MSGPACK_OBJECT_POSITIVE_INTEGER &&
|
||||
obj->type != MSGPACK_OBJECT_NEGATIVE_INTEGER) {
|
||||
return false;
|
||||
}
|
||||
if (i != NULL) *i = obj->via.i64;
|
||||
return true;
|
||||
}
|
||||
|
||||
#define define_mpkutil_get_intN_(N) \
|
||||
bool mpkutil_get_int##N(const msgpack_object* obj, int##N##_t* i) { \
|
||||
intmax_t temp; \
|
||||
if (!mpkutil_get_int(obj, &temp) || \
|
||||
temp < INT##N##_MIN || temp > INT##N##_MAX) { \
|
||||
return false; \
|
||||
} \
|
||||
if (i != NULL) *i = temp; \
|
||||
return true; \
|
||||
}
|
||||
define_mpkutil_get_intN_(8);
|
||||
define_mpkutil_get_intN_(16);
|
||||
define_mpkutil_get_intN_(32);
|
||||
define_mpkutil_get_intN_(64);
|
||||
#undef define_mpkutil_get_intN_
|
||||
|
||||
bool mpkutil_get_uint(const msgpack_object* obj, uintmax_t* i) {
|
||||
if (obj == NULL || obj->type != MSGPACK_OBJECT_POSITIVE_INTEGER) {
|
||||
return false;
|
||||
}
|
||||
if (i != NULL) *i = obj->via.u64;
|
||||
return true;
|
||||
}
|
||||
|
||||
#define define_mpkutil_get_uintN_(N) \
|
||||
bool mpkutil_get_uint##N(const msgpack_object* obj, uint##N##_t* i) { \
|
||||
uintmax_t temp; \
|
||||
if (!mpkutil_get_uint(obj, &temp) || temp > UINT##N##_MAX) { \
|
||||
return false; \
|
||||
} \
|
||||
if (i != NULL) *i = temp; \
|
||||
return true; \
|
||||
}
|
||||
define_mpkutil_get_uintN_(8);
|
||||
define_mpkutil_get_uintN_(16);
|
||||
define_mpkutil_get_uintN_(32);
|
||||
define_mpkutil_get_uintN_(64);
|
||||
#undef define_mpkutil_get_uintN_
|
||||
|
||||
bool mpkutil_get_float(const msgpack_object* obj, float* f) {
|
||||
if (obj == NULL || obj->type != MSGPACK_OBJECT_FLOAT64 ||
|
||||
!MATH_FLOAT_VALID(obj->via.f64)) {
|
||||
return false;
|
||||
}
|
||||
if (f != NULL) *f = obj->via.f64;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mpkutil_get_str(const msgpack_object* obj, const char** s, size_t* len) {
|
||||
if (obj == NULL || obj->type != MSGPACK_OBJECT_STR) {
|
||||
return false;
|
||||
}
|
||||
if (s != NULL) *s = obj->via.str.ptr;
|
||||
if (len != NULL) *len = obj->via.str.size;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mpkutil_get_vec2(const msgpack_object* obj, vec2_t* v) {
|
||||
const msgpack_object_array* array = mpkutil_get_array(obj);
|
||||
if (array == NULL || array->size != 2) return false;
|
||||
for (size_t i = 0; i < 2; ++i) {
|
||||
if (array->ptr[i].type != MSGPACK_OBJECT_FLOAT64) return false;
|
||||
}
|
||||
if (v != NULL) {
|
||||
v->x = array->ptr[0].via.f64;
|
||||
v->y = array->ptr[1].via.f64;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mpkutil_get_vec4(const msgpack_object* obj, vec4_t* v) {
|
||||
const msgpack_object_array* array = mpkutil_get_array(obj);
|
||||
if (array == NULL || array->size != 4) return false;
|
||||
for (size_t i = 0; i < 4; ++i) {
|
||||
if (array->ptr[i].type != MSGPACK_OBJECT_FLOAT64) return false;
|
||||
}
|
||||
if (v != NULL) {
|
||||
v->x = array->ptr[0].via.f64;
|
||||
v->y = array->ptr[1].via.f64;
|
||||
v->y = array->ptr[2].via.f64;
|
||||
v->y = array->ptr[3].via.f64;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const msgpack_object_array* mpkutil_get_array(const msgpack_object* obj) {
|
||||
if (obj == NULL || obj->type != MSGPACK_OBJECT_ARRAY) {
|
||||
return NULL;
|
||||
}
|
||||
return &obj->via.array;
|
||||
}
|
||||
|
||||
const msgpack_object_map* mpkutil_get_map(const msgpack_object* obj) {
|
||||
if (obj == NULL || obj->type != MSGPACK_OBJECT_MAP) {
|
||||
return NULL;
|
||||
}
|
||||
return &obj->via.map;
|
||||
}
|
||||
|
||||
const msgpack_object* mpkutil_get_map_item_by_str(
|
||||
const msgpack_object_map* map, const char* name) {
|
||||
if (map == NULL) return NULL;
|
||||
|
||||
for (size_t i = 0; i < map->size; ++i) {
|
||||
const msgpack_object* key_obj = &map->ptr[i].key;
|
||||
if (key_obj->type != MSGPACK_OBJECT_STR) continue;
|
||||
|
||||
const msgpack_object_str* key = &key_obj->via.str;
|
||||
if (strncmp(key->ptr, name, key->size) == 0 && name[key->size] == 0) {
|
||||
return &map->ptr[i].val;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
92
util/mpkutil/get.h
Normal file
92
util/mpkutil/get.h
Normal file
@@ -0,0 +1,92 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <msgpack.h>
|
||||
|
||||
#include "util/math/vector.h"
|
||||
|
||||
bool
|
||||
mpkutil_get_bool(
|
||||
const msgpack_object* obj, /* NULLABLE */
|
||||
bool* b /* NULLABLE */
|
||||
);
|
||||
|
||||
bool
|
||||
mpkutil_get_int(
|
||||
const msgpack_object* obj, /* NULLABLE */
|
||||
intmax_t* i /* NULLABLE */
|
||||
);
|
||||
|
||||
#define decl_mpkutil_get_intN_(N) \
|
||||
bool \
|
||||
mpkutil_get_int##N( \
|
||||
const msgpack_object* obj, \
|
||||
int##N##_t* i \
|
||||
);
|
||||
decl_mpkutil_get_intN_(8);
|
||||
decl_mpkutil_get_intN_(16);
|
||||
decl_mpkutil_get_intN_(32);
|
||||
decl_mpkutil_get_intN_(64);
|
||||
#undef decl_mpkutil_get_intN_
|
||||
|
||||
bool
|
||||
mpkutil_get_uint(
|
||||
const msgpack_object* obj, /* NULLABLE */
|
||||
uintmax_t* i /* NULLABLE */
|
||||
);
|
||||
|
||||
#define decl_mpkutil_get_uintN_(N) \
|
||||
bool \
|
||||
mpkutil_get_uint##N( \
|
||||
const msgpack_object* obj, \
|
||||
uint##N##_t* i \
|
||||
);
|
||||
decl_mpkutil_get_uintN_(8);
|
||||
decl_mpkutil_get_uintN_(16);
|
||||
decl_mpkutil_get_uintN_(32);
|
||||
decl_mpkutil_get_uintN_(64);
|
||||
#undef decl_mpkutil_get_uintN_
|
||||
|
||||
bool
|
||||
mpkutil_get_float(
|
||||
const msgpack_object* obj, /* NULLABLE */
|
||||
float* f /* NULLABLE */
|
||||
);
|
||||
|
||||
bool
|
||||
mpkutil_get_str(
|
||||
const msgpack_object* obj, /* NULLABLE */
|
||||
const char** s, /* NULLABLE */
|
||||
size_t* len /* NULLABLE */
|
||||
);
|
||||
|
||||
bool
|
||||
mpkutil_get_vec2(
|
||||
const msgpack_object* obj, /* NULLABLE */
|
||||
vec2_t* v /* NULLABLE */
|
||||
);
|
||||
|
||||
bool
|
||||
mpkutil_get_vec4(
|
||||
const msgpack_object* obj, /* NULLABLE */
|
||||
vec4_t* v /* NULLABLE */
|
||||
);
|
||||
|
||||
const msgpack_object_array* /* NULLABLE */
|
||||
mpkutil_get_array(
|
||||
const msgpack_object* obj /* NULLABLE */
|
||||
);
|
||||
|
||||
const msgpack_object_map* /* NULLABLE */
|
||||
mpkutil_get_map(
|
||||
const msgpack_object* obj /* NULLABLE */
|
||||
);
|
||||
|
||||
const msgpack_object* /* NULLABLE */
|
||||
mpkutil_get_map_item_by_str(
|
||||
const msgpack_object_map* map, /* NULLABLE */
|
||||
const char* name /* NULL-terminated */
|
||||
);
|
49
util/mpkutil/pack.c
Normal file
49
util/mpkutil/pack.c
Normal file
@@ -0,0 +1,49 @@
|
||||
#include "./pack.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include <msgpack.h>
|
||||
|
||||
#include "util/math/vector.h"
|
||||
|
||||
void mpkutil_pack_bool(msgpack_packer* packer, bool b) {
|
||||
assert(packer != NULL);
|
||||
|
||||
(b? msgpack_pack_true: msgpack_pack_false)(packer);
|
||||
}
|
||||
|
||||
void mpkutil_pack_str(msgpack_packer* packer, const char* str) {
|
||||
assert(packer != NULL);
|
||||
assert(str != NULL);
|
||||
|
||||
mpkutil_pack_strn(packer, str, strlen(str));
|
||||
}
|
||||
|
||||
void mpkutil_pack_strn(msgpack_packer* packer, const char* str, size_t len) {
|
||||
assert(packer != NULL);
|
||||
assert(str != NULL);
|
||||
|
||||
msgpack_pack_str(packer, len);
|
||||
msgpack_pack_str_body(packer, str, len);
|
||||
}
|
||||
|
||||
void mpkutil_pack_vec2(msgpack_packer* packer, const vec2_t* v) {
|
||||
assert(packer != NULL);
|
||||
assert(v != NULL);
|
||||
|
||||
msgpack_pack_array(packer, 2);
|
||||
msgpack_pack_double(packer, v->x);
|
||||
msgpack_pack_double(packer, v->y);
|
||||
}
|
||||
|
||||
void mpkutil_pack_vec4(msgpack_packer* packer, const vec4_t* v) {
|
||||
assert(packer != NULL);
|
||||
assert(v != NULL);
|
||||
|
||||
msgpack_pack_array(packer, 4);
|
||||
msgpack_pack_double(packer, v->x);
|
||||
msgpack_pack_double(packer, v->y);
|
||||
msgpack_pack_double(packer, v->z);
|
||||
msgpack_pack_double(packer, v->w);
|
||||
}
|
39
util/mpkutil/pack.h
Normal file
39
util/mpkutil/pack.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include <msgpack.h>
|
||||
|
||||
#include "util/math/vector.h"
|
||||
|
||||
void
|
||||
mpkutil_pack_bool(
|
||||
msgpack_packer* packer,
|
||||
bool v
|
||||
);
|
||||
|
||||
void
|
||||
mpkutil_pack_str(
|
||||
msgpack_packer* packer,
|
||||
const char* str /* NULL-terminated */
|
||||
);
|
||||
|
||||
void
|
||||
mpkutil_pack_strn(
|
||||
msgpack_packer* packer,
|
||||
const char* str,
|
||||
size_t len
|
||||
);
|
||||
|
||||
void
|
||||
mpkutil_pack_vec2(
|
||||
msgpack_packer* packer,
|
||||
const vec2_t* v
|
||||
);
|
||||
|
||||
void
|
||||
mpkutil_pack_vec4(
|
||||
msgpack_packer* packer,
|
||||
const vec4_t* v
|
||||
);
|
12
util/parsarg/CMakeLists.txt
Normal file
12
util/parsarg/CMakeLists.txt
Normal file
@@ -0,0 +1,12 @@
|
||||
add_library(parsarg
|
||||
parsarg.c
|
||||
)
|
||||
|
||||
if (BUILD_TESTING)
|
||||
add_executable(parsarg-test test.c)
|
||||
target_link_libraries(parsarg-test
|
||||
parsarg
|
||||
|
||||
conv
|
||||
)
|
||||
endif()
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user