library: animation and bone APIs

This commit is contained in:
Adam
2024-08-26 04:45:18 +01:00
parent da4946ad5a
commit 2d8dcb087a
5 changed files with 155 additions and 2 deletions

View File

@@ -86,6 +86,8 @@ static size_t _bolt_gl_plugin_drawelements_vertex3d_atlas_meta(size_t index, voi
static void _bolt_gl_plugin_drawelements_vertex3d_meta_xywh(size_t meta, void* userdata, int32_t* out);
static void _bolt_gl_plugin_drawelements_vertex3d_uv(size_t index, void* userdata, double* out);
static void _bolt_gl_plugin_drawelements_vertex3d_colour(size_t index, void* userdata, double* out);
static uint8_t _bolt_gl_plugin_drawelements_vertex3d_boneid(size_t index, void* userdata);
static void _bolt_gl_plugin_bone_transform(uint8_t bone_id, void* userdata, double* out);
static void _bolt_gl_plugin_matrix3d_toworldspace(int x, int y, int z, void* userdata, double* out);
static void _bolt_gl_plugin_matrix3d_toscreenspace(int x, int y, int z, void* userdata, double* out);
static void _bolt_gl_plugin_matrix3d_worldpos(void* userdata, double* out);
@@ -606,6 +608,7 @@ static GLuint _bolt_glCreateProgram() {
program->loc_uTextureAtlasSettings = -1;
program->loc_uAtlasMeta = -1;
program->loc_uModelMatrix = -1;
program->loc_uBoneTransforms = -1;
program->loc_uGridSize = -1;
program->loc_uVertexScale = -1;
program->loc_sSceneHDRTex = -1;
@@ -666,6 +669,7 @@ static void _bolt_glLinkProgram(GLuint program) {
const GLint loc_uVertexScale = gl.GetUniformLocation(program, "uVertexScale");
p->loc_sSceneHDRTex = gl.GetUniformLocation(program, "sSceneHDRTex");
p->loc_sSourceTex = gl.GetUniformLocation(program, "sSourceTex");
p->loc_uBoneTransforms = gl.GetUniformLocation(program, "uBoneTransforms");
const GLchar* view_var_names[] = {"uCameraPosition", "uViewProjMatrix"};
const GLuint block_index_ViewTransforms = gl.GetUniformBlockIndex(program, "ViewTransforms");
@@ -1375,12 +1379,15 @@ void _bolt_gl_onDrawElements(GLenum mode, GLsizei count, GLenum type, const void
struct Render3D render;
render.vertex_count = count;
render.is_animated = c->bound_program->loc_uBoneTransforms != -1;
render.vertex_functions.userdata = &vertex_userdata;
render.vertex_functions.xyz = _bolt_gl_plugin_drawelements_vertex3d_xyz;
render.vertex_functions.atlas_meta = _bolt_gl_plugin_drawelements_vertex3d_atlas_meta;
render.vertex_functions.atlas_xywh = _bolt_gl_plugin_drawelements_vertex3d_meta_xywh;
render.vertex_functions.uv = _bolt_gl_plugin_drawelements_vertex3d_uv;
render.vertex_functions.colour = _bolt_gl_plugin_drawelements_vertex3d_colour;
render.vertex_functions.bone_id = _bolt_gl_plugin_drawelements_vertex3d_boneid;
render.vertex_functions.bone_transform = _bolt_gl_plugin_bone_transform;
render.texture_functions.userdata = &tex_userdata;
render.texture_functions.id = _bolt_gl_plugin_texture_id;
render.texture_functions.size = _bolt_gl_plugin_texture_size;
@@ -1588,6 +1595,42 @@ static void _bolt_gl_plugin_drawelements_vertex3d_colour(size_t index, void* use
out[3] = (double)colour[0];
}
static uint8_t _bolt_gl_plugin_drawelements_vertex3d_boneid(size_t index, void* userdata) {
struct GLPluginDrawElementsVertex3DUserData* data = userdata;
uint32_t ret[4];
if (!_bolt_get_attr_binding_int(data->c, data->xyz_bone, data->indices[index], 4, (int32_t*)&ret)) {
float retf[4];
_bolt_get_attr_binding(data->c, data->xyz_bone, data->indices[index], 4, retf);
return (uint8_t)(uint32_t)(int32_t)roundf(retf[3]);
}
return (uint8_t)(ret[3]);
}
static void _bolt_gl_plugin_bone_transform(uint8_t bone_id, void* userdata, double* out) {
struct GLContext* c = _bolt_context();
const GLint uniform_loc = c->bound_program->loc_uBoneTransforms + (bone_id * 3);
GLfloat f[4];
gl.GetUniformfv(c->bound_program->id, uniform_loc, f);
out[0] = (double)f[0];
out[1] = (double)f[1];
out[2] = (double)f[2];
out[3] = 0.0;
out[4] = (double)f[3];
gl.GetUniformfv(c->bound_program->id, uniform_loc + 1, f);
out[5] = (double)f[0];
out[6] = (double)f[1];
out[7] = 0.0;
out[8] = (double)f[2];
out[9] = (double)f[3];
gl.GetUniformfv(c->bound_program->id, uniform_loc + 2, f);
out[10] = (double)f[0];
out[11] = 0.0;
out[12] = (double)f[1];
out[13] = (double)f[2];
out[14] = (double)f[3];
out[15] = 1.0;
}
static void _bolt_gl_plugin_matrix3d_toworldspace(int x, int y, int z, void* userdata, double* out) {
const struct GLPlugin3DMatrixUserData* data = userdata;
const double dx = (double)x;

View File

@@ -193,6 +193,7 @@ struct GLProgram {
GLint loc_uTextureAtlasSettings;
GLint loc_uAtlasMeta;
GLint loc_uModelMatrix;
GLint loc_uBoneTransforms;
GLint loc_uGridSize;
GLint loc_uVertexScale;
GLint loc_sSceneHDRTex;

View File

@@ -8,6 +8,7 @@
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
@@ -628,7 +629,7 @@ uint8_t _bolt_plugin_add(const char* path, struct Plugin* plugin) {
PUSHSTRING(plugin->state, RENDER3D_META_REGISTRYNAME);
lua_newtable(plugin->state);
PUSHSTRING(plugin->state, "__index");
lua_createtable(plugin->state, 0, 14);
lua_createtable(plugin->state, 0, 17);
API_ADD_SUB(plugin->state, vertexcount, render3d)
API_ADD_SUB(plugin->state, vertexxyz, render3d)
API_ADD_SUB(plugin->state, vertexmeta, render3d)
@@ -642,6 +643,9 @@ uint8_t _bolt_plugin_add(const char* path, struct Plugin* plugin) {
API_ADD_SUB(plugin->state, toworldspace, render3d)
API_ADD_SUB(plugin->state, toscreenspace, render3d)
API_ADD_SUB(plugin->state, worldposition, render3d)
API_ADD_SUB(plugin->state, vertexbone, render3d)
API_ADD_SUB(plugin->state, bonetransforms, render3d)
API_ADD_SUB(plugin->state, animated, render3d)
API_ADD_SUB_ALIAS(plugin->state, vertexcolour, vertexcolor, render3d)
lua_settable(plugin->state, -3);
lua_settable(plugin->state, LUA_REGISTRYINDEX);
@@ -1554,6 +1558,69 @@ static int api_render3d_worldposition(lua_State* state) {
return 3;
}
static int api_render3d_vertexbone(lua_State* state) {
_bolt_check_argc(state, 2, "render3d_vertexbone");
struct Render3D* render = lua_touserdata(state, 1);
const int index = lua_tointeger(state, 2);
uint8_t ret = render->vertex_functions.bone_id(index, render->vertex_functions.userdata);
lua_pushinteger(state, ret);
return 1;
}
static int api_render3d_bonetransforms(lua_State* state) {
_bolt_check_argc(state, 2, "render3d_bonetransforms");
struct Render3D* render = lua_touserdata(state, 1);
const int bone_id = lua_tointeger(state, 2);
if (!render->is_animated) {
PUSHSTRING(state, "render3d_bonetransforms: cannot get bone transforms for non-animated model");
lua_error(state);
}
// not a mistake - game's shader supports values from 0 to 128, inclusive
if (bone_id < 0 || bone_id > 128) {
PUSHSTRING(state, "render3d_bonetransforms: invalid bone ID");
lua_error(state);
}
#define LENGTH(N1, N2, N3) sqrt((N1 * N1) + (N2 * N2) + (N3 * N3))
double transform[16];
render->vertex_functions.bone_transform((uint8_t)bone_id, render->vertex_functions.userdata, transform);
lua_pushnumber(state, transform[12]);
lua_pushnumber(state, transform[13]);
lua_pushnumber(state, transform[14]);
const double scale_x = LENGTH(transform[0], transform[1], transform[2]);
const double scale_y = LENGTH(transform[4], transform[5], transform[6]);
const double scale_z = LENGTH(transform[8], transform[9], transform[10]);
lua_pushnumber(state, scale_x);
lua_pushnumber(state, scale_y);
lua_pushnumber(state, scale_z);
if (scale_x == 0.0 || scale_y == 0.0 || scale_z == 0.0) {
// scale=0 means we can't calculate angles because there would be a division by zero,
// and angle is meaningless for a zero-scale model anyway, so just put 0 for all the angles
lua_pushnumber(state, 0.0);
lua_pushnumber(state, 0.0);
lua_pushnumber(state, 0.0);
} else {
// see https://stackoverflow.com/questions/39128589/decomposing-rotation-matrix-x-y-z-cartesian-angles
const double yaw = atan2(-(transform[8] / scale_z), sqrt((transform[0] * transform[0] / (scale_x * scale_x)) + (transform[4] * transform[4] / (scale_y * scale_y))));
const double pitch = atan2((transform[9] / scale_z) / cos(yaw), (transform[10] / scale_z) / cos(yaw));
const double roll = atan2((transform[4] / scale_y) / cos(yaw), (transform[0] / scale_x) / cos(yaw));
lua_pushnumber(state, yaw);
lua_pushnumber(state, pitch);
lua_pushnumber(state, roll);
}
return 9;
#undef LENGTH
}
static int api_render3d_animated(lua_State* state) {
_bolt_check_argc(state, 1, "render3d_animated");
struct Render3D* render = lua_touserdata(state, 1);
lua_pushboolean(state, render->is_animated);
return 1;
}
static int api_resizeevent_size(lua_State* state) {
_bolt_check_argc(state, 1, "resizeevent_size");
struct ResizeEvent* event = lua_touserdata(state, 1);

View File

@@ -58,6 +58,12 @@ struct Vertex3DFunctions {
/// Returns the RGBA colour of this vertex, each one normalised from 0.0 to 1.0.
void (*colour)(size_t index, void* userdata, double* out);
/// Returns the ID of the bone this vertex belongs to.
uint8_t (*bone_id)(size_t index, void* userdata);
/// Returns the transform matrix for the given bone, as 16 doubles.
void (*bone_transform)(uint8_t bone_id, void* userdata, double* out);
};
/// Struct containing "vtable" callback information for textures.
@@ -199,6 +205,7 @@ struct RenderBatch2D {
struct Render3D {
uint32_t vertex_count;
uint8_t is_animated;
struct Vertex3DFunctions vertex_functions;
struct TextureFunctions texture_functions;
struct Render3DMatrixFunctions matrix_functions;

View File

@@ -484,7 +484,9 @@ static int api_window_onscroll(lua_State*);
static int api_render3d_vertexcount(lua_State*);
/// [-2, +3, -]
/// Given an index of a vertex in a model, returns its X Y and Z in model coordinates.
/// Given an index of a vertex in a model, returns its X Y and Z in model coordinates. More
/// specifically, this is the default position of this vertex in the model - it is not affected by
/// any kind of scaling, rotation, movement, or animation that may be happening to the model.
static int api_render3d_vertexxyz(lua_State*);
/// [-2, +1, -]
@@ -571,6 +573,39 @@ static int api_render3d_toscreenspace(lua_State*);
/// Equivalent to `render:toworldspace(0, 0, 0)`
static int api_render3d_worldposition(lua_State*);
/// [-1, +1, -]
/// Returns the bone ID of this vertex. Animated models have multiple bones which can move
/// independently of each other, and this function can be used to find out which bone a vertex
/// belongs to. The returned value may be any integer from 0 to 255, although the game engine
/// actually seems to be unable to handle indices higher than 128. (128 itself is valid.)
///
/// All vertices have bone IDs, even in non-animated models, so plugins may call this function
/// regardless of whether the model is animated or not. For a non-animated model the bone ID seems
/// to be meaningless and is usually 0. To check if the model is animated, use `animated()`.
static int api_render3d_vertexbone(lua_State*);
/// [-1, +9, -]
/// Given a bone ID, returns the following nine floating-point values in this order: translation X,
/// Y and Z, in model coordinates; scale factor X, Y and Z; yaw, pitch, and roll, in radians. These
/// values represent the animation state of this bone during this render.
///
/// It is a fatal error to call this function on a render event for a non-animated model, since
/// non-animated models have no bone transforms that could be queried. To check if the model is
/// animated, use `animated()`.
///
/// The results of this function are imperfect for two reasons. Firstly, the engine doesn't use
/// static animation frames; instead it interpolates between multiple frames, so the exact state of
/// animation will depend on the user's FPS. Secondly, these values get pre-multiplied into one big
/// matrix by the time Bolt can access them. Bolt decomposes the matrix back to its original values
/// but there will be some loss of precision.
static int api_render3d_bonetransforms(lua_State*);
/// [-1, +1, -]
/// Returns a boolean value indicating whether this model is animated. Animated models can have
/// multiple bones which can move independently of each other. For more information on bones, see
/// `vertexbone()` and `bonetransforms()`.
static int api_render3d_animated(lua_State*);
/// [-1, +2, -]
/// Returns the new width and height that the window was resized to.
static int api_resizeevent_size(lua_State*);