[glfw] MVP

This commit is contained in:
Mario Zechner 2024-06-26 20:17:01 +02:00
parent 99d6693979
commit 4b5102fddf
12 changed files with 17507 additions and 2 deletions

View File

@ -40,6 +40,9 @@ elseif((${SPINE_SDL}) OR (${CMAKE_CURRENT_BINARY_DIR} MATCHES "spine-sdl"))
add_subdirectory(spine-c)
add_subdirectory(spine-cpp)
add_subdirectory(spine-sdl)
elseif((${SPINE_GLFW}) OR (${CMAKE_CURRENT_BINARY_DIR} MATCHES "spine-glfw"))
add_subdirectory(spine-cpp)
add_subdirectory(spine-glfw)
else()
add_subdirectory(spine-c)
add_subdirectory(spine-cpp)

View File

@ -15,7 +15,7 @@ add_library(spine-cpp STATIC ${SOURCES} ${INCLUDES})
target_include_directories(spine-cpp PUBLIC spine-cpp/include)
add_library(spine-cpp-lite STATIC ${SOURCES} ${INCLUDES} spine-cpp-lite/spine-cpp-lite.cpp)
target_include_directories(spine-cpp-lite PUBLIC spine-cpp/include)
target_include_directories(spine-cpp-lite PUBLIC spine-cpp/include spine-cpp-lite)
# Install target
install(TARGETS spine-cpp EXPORT spine-cpp_TARGETS DESTINATION dist/lib)

View File

@ -135,6 +135,7 @@ typedef struct _spine_render_command {
float *positions;
float *uvs;
int32_t *colors;
int32_t *darkColors;
int32_t numVertices;
uint16_t *indices;
int32_t numIndices;
@ -653,6 +654,7 @@ static _spine_render_command *spine_render_command_create(BlockAllocator &alloca
cmd->positions = allocator.allocate<float>(numVertices << 1);
cmd->uvs = allocator.allocate<float>(numVertices << 1);
cmd->colors = allocator.allocate<int32_t>(numVertices);
cmd->darkColors = allocator.allocate<int32_t>(numVertices);
cmd->numVertices = numVertices;
cmd->indices = allocator.allocate<uint16_t>(numIndices);
cmd->numIndices = numIndices;
@ -697,6 +699,7 @@ static _spine_render_command *batch_sub_commands(BlockAllocator &allocator, Vect
float *positions = batched->positions;
float *uvs = batched->uvs;
int32_t *colors = batched->colors;
int32_t *darkColors = batched->darkColors;
uint16_t *indices = batched->indices;
int indicesOffset = 0;
for (int i = first; i <= last; i++) {
@ -704,12 +707,14 @@ static _spine_render_command *batch_sub_commands(BlockAllocator &allocator, Vect
memcpy(positions, cmd->positions, sizeof(float) * 2 * cmd->numVertices);
memcpy(uvs, cmd->uvs, sizeof(float) * 2 * cmd->numVertices);
memcpy(colors, cmd->colors, sizeof(int32_t) * cmd->numVertices);
memcpy(darkColors, cmd->darkColors, sizeof(int32_t) * cmd->numVertices);
for (int ii = 0; ii < cmd->numIndices; ii++)
indices[ii] = cmd->indices[ii] + indicesOffset;
indicesOffset += cmd->numVertices;
positions += 2 * cmd->numVertices;
uvs += 2 * cmd->numVertices;
colors += cmd->numVertices;
darkColors += cmd->numVertices;
indices += cmd->numIndices;
}
return batched;
@ -728,9 +733,16 @@ static _spine_render_command *batch_commands(BlockAllocator &allocator, Vector<_
int numIndices = first->numIndices;
while (i <= (int) commands.size()) {
_spine_render_command *cmd = i < (int) commands.size() ? commands[i] : nullptr;
if (cmd && cmd->numVertices == 0 && cmd->numIndices == 0) {
i++;
continue;
}
if (cmd != nullptr && cmd->atlasPage == first->atlasPage &&
cmd->blendMode == first->blendMode &&
cmd->colors[0] == first->colors[0] &&
cmd->darkColors[0] == first->darkColors[0] &&
numIndices + cmd->numIndices < 0xffff) {
numVertices += cmd->numVertices;
numIndices += cmd->numIndices;
@ -836,6 +848,11 @@ spine_render_command spine_skeleton_drawable_render(spine_skeleton_drawable draw
uint8_t b = static_cast<uint8_t>(skeleton->getColor().b * slot.getColor().b * attachmentColor->b * 255);
uint8_t a = static_cast<uint8_t>(skeleton->getColor().a * slot.getColor().a * attachmentColor->a * 255);
uint32_t color = (a << 24) | (r << 16) | (g << 8) | b;
uint32_t darkColor = 0xff000000;
if (slot.hasDarkColor()) {
Color &slotDarkColor = slot.getDarkColor();
darkColor = 0xff000000 | (static_cast<uint8_t>(slotDarkColor.r * 255) << 16)| (static_cast<uint8_t>(slotDarkColor.g * 255) << 8)| static_cast<uint8_t>(slotDarkColor.b * 255);
}
if (clipper.isClipping()) {
clipper.clipTriangles(*worldVertices, *indices, *uvs, 2);
@ -850,7 +867,10 @@ spine_render_command spine_skeleton_drawable_render(spine_skeleton_drawable draw
_drawable->renderCommands.add(cmd);
memcpy(cmd->positions, vertices->buffer(), (verticesCount << 1) * sizeof(float));
memcpy(cmd->uvs, uvs->buffer(), (verticesCount << 1) * sizeof(float));
for (int ii = 0; ii < verticesCount; ii++) cmd->colors[ii] = color;
for (int ii = 0; ii < verticesCount; ii++) {
cmd->colors[ii] = color;
cmd->darkColors[ii] = darkColor;
}
memcpy(cmd->indices, indices->buffer(), indices->size() * sizeof(uint16_t));
clipper.clipEnd(slot);
}
@ -895,6 +915,11 @@ int32_t *spine_render_command_get_colors(spine_render_command command) {
return ((_spine_render_command *) command)->colors;
}
int32_t *spine_render_command_get_dark_colors(spine_render_command command) {
if (!command) return nullptr;
return ((_spine_render_command *) command)->darkColors;
}
int32_t spine_render_command_get_num_vertices(spine_render_command command) {
if (!command) return 0;
return ((_spine_render_command *) command)->numVertices;
@ -1697,6 +1722,13 @@ void spine_skeleton_set_y(spine_skeleton skeleton, float y) {
_skeleton->setY(y);
}
void spine_skeleton_set_scale(spine_skeleton skeleton, float scaleX, float scaleY) {
if (skeleton == nullptr) return;
Skeleton *_skeleton = (Skeleton *) skeleton;
_skeleton->setScaleX(scaleX);
_skeleton->setScaleY(scaleY);
}
float spine_skeleton_get_scale_x(spine_skeleton skeleton) {
if (skeleton == nullptr) return 0;
Skeleton *_skeleton = (Skeleton *) skeleton;

View File

@ -292,6 +292,8 @@ SPINE_CPP_LITE_EXPORT float *spine_render_command_get_positions(spine_render_com
SPINE_CPP_LITE_EXPORT float *spine_render_command_get_uvs(spine_render_command command);
// @ignore
SPINE_CPP_LITE_EXPORT int32_t *spine_render_command_get_colors(spine_render_command command);
// @ignore
SPINE_CPP_LITE_EXPORT int32_t *spine_render_command_get_dark_colors(spine_render_command command);
SPINE_CPP_LITE_EXPORT int32_t spine_render_command_get_num_vertices(spine_render_command command);
SPINE_CPP_LITE_EXPORT uint16_t *spine_render_command_get_indices(spine_render_command command);
SPINE_CPP_LITE_EXPORT int32_t spine_render_command_get_num_indices(spine_render_command command);
@ -452,6 +454,7 @@ SPINE_CPP_LITE_EXPORT float spine_skeleton_get_x(spine_skeleton skeleton);
SPINE_CPP_LITE_EXPORT void spine_skeleton_set_x(spine_skeleton skeleton, float x);
SPINE_CPP_LITE_EXPORT float spine_skeleton_get_y(spine_skeleton skeleton);
SPINE_CPP_LITE_EXPORT void spine_skeleton_set_y(spine_skeleton skeleton, float y);
SPINE_CPP_LITE_EXPORT void spine_skeleton_set_scale(spine_skeleton skeleton, float scaleX, float scaleY);
SPINE_CPP_LITE_EXPORT float spine_skeleton_get_scale_x(spine_skeleton skeleton);
SPINE_CPP_LITE_EXPORT void spine_skeleton_set_scale_x(spine_skeleton skeleton, float scaleX);
SPINE_CPP_LITE_EXPORT float spine_skeleton_get_scale_y(spine_skeleton skeleton);

61
spine-glfw/CMakeLists.txt Normal file
View File

@ -0,0 +1,61 @@
cmake_minimum_required(VERSION 3.10)
project(spine-glfw)
if(MSVC)
message("MSCV detected")
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_CRT_SECURE_NO_WARNINGS")
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_CRT_SECURE_NO_WARNINGS")
else()
message("${CMAKE_C_FLAGS}")
message("${CMAKE_CXX_FLAGS}")
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -pedantic -Wno-unused-parameter -std=c99")
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wnon-virtual-dtor -pedantic -Wno-unused-parameter -std=c++11 -fno-exceptions -fno-rtti")
endif()
include(FetchContent)
# Fetch GLFW
FetchContent_Declare(
glfw
GIT_REPOSITORY https://github.com/glfw/glfw.git
GIT_TAG latest
)
FetchContent_MakeAvailable(glfw)
set(GLFW_BUILD_EXAMPLES OFF CACHE BOOL " " FORCE)
set(GLFW_BUILD_TESTS OFF CACHE BOOL " " FORCE)
set(GLFW_BUILD_DOCS OFF CACHE BOOL " " FORCE)
set(GLFW_INSTALL OFF CACHE BOOL " " FORCE)
# Fetch glbinding
FetchContent_Declare(
glbinding
GIT_REPOSITORY https://github.com/cginternals/glbinding.git
GIT_TAG master
)
FetchContent_MakeAvailable(glbinding)
set(OPTION_BUILD_DOCS OFF CACHE BOOL " " FORCE)
set(OPTION_BUILD_EXAMPLES OFF CACHE BOOL " " FORCE)
set(OPTION_BUILD_TEST OFF CACHE BOOL " " FORCE)
set(OPTION_BUILD_TOOLS OFF CACHE BOOL " " FORCE)
include_directories(${glbinding_SOURCE_DIR}/include)
include_directories(src)
# Find local OpenGL lib
find_package(OpenGL REQUIRED)
# spine-glfw library
add_library(spine-glfw STATIC src/spine-glfw.cpp src/spine-glfw.h src/stb_image.h)
target_link_libraries(spine-glfw PRIVATE glbinding::glbinding)
target_link_libraries(spine-glfw LINK_PUBLIC spine-cpp spine-cpp-lite)
# Example
add_executable(spine-glfw-example example/main.cpp)
target_link_libraries(spine-glfw-example PRIVATE glfw)
target_link_libraries(spine-glfw-example PRIVATE OpenGL::GL)
target_link_libraries(spine-glfw-example LINK_PUBLIC spine-glfw)
target_link_libraries(spine-glfw-example PRIVATE glbinding::glbinding)
# copy data to build directory
add_custom_command(TARGET spine-glfw-example PRE_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_CURRENT_LIST_DIR}/data $<TARGET_FILE_DIR:spine-glfw-example>/data)

View File

@ -0,0 +1,95 @@
spineboy-pma.png
size: 1024, 256
filter: Linear, Linear
pma: true
scale: 0.5
crosshair
bounds: 352, 7, 45, 45
eye-indifferent
bounds: 862, 105, 47, 45
eye-surprised
bounds: 505, 79, 47, 45
front-bracer
bounds: 826, 66, 29, 40
front-fist-closed
bounds: 786, 65, 38, 41
front-fist-open
bounds: 710, 51, 43, 44
rotate: 90
front-foot
bounds: 210, 6, 63, 35
front-shin
bounds: 665, 128, 41, 92
rotate: 90
front-thigh
bounds: 2, 2, 23, 56
rotate: 90
front-upper-arm
bounds: 250, 205, 23, 49
goggles
bounds: 665, 171, 131, 83
gun
bounds: 798, 152, 105, 102
head
bounds: 2, 27, 136, 149
hoverboard-board
bounds: 2, 178, 246, 76
hoverboard-thruster
bounds: 722, 96, 30, 32
rotate: 90
hoverglow-small
bounds: 275, 81, 137, 38
mouth-grind
bounds: 614, 97, 47, 30
mouth-oooo
bounds: 612, 65, 47, 30
mouth-smile
bounds: 661, 64, 47, 30
muzzle-glow
bounds: 382, 54, 25, 25
muzzle-ring
bounds: 275, 54, 25, 105
rotate: 90
muzzle01
bounds: 911, 95, 67, 40
rotate: 90
muzzle02
bounds: 792, 108, 68, 42
muzzle03
bounds: 956, 171, 83, 53
rotate: 90
muzzle04
bounds: 275, 7, 75, 45
muzzle05
bounds: 140, 3, 68, 38
neck
bounds: 250, 182, 18, 21
portal-bg
bounds: 140, 43, 133, 133
portal-flare1
bounds: 554, 65, 56, 30
portal-flare2
bounds: 759, 112, 57, 31
rotate: 90
portal-flare3
bounds: 554, 97, 58, 30
portal-shade
bounds: 275, 121, 133, 133
portal-streaks1
bounds: 410, 126, 126, 128
portal-streaks2
bounds: 538, 129, 125, 125
rear-bracer
bounds: 857, 67, 28, 36
rear-foot
bounds: 663, 96, 57, 30
rear-shin
bounds: 414, 86, 38, 89
rotate: 90
rear-thigh
bounds: 756, 63, 28, 47
rear-upper-arm
bounds: 60, 5, 20, 44
rotate: 90
torso
bounds: 905, 164, 49, 90

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 KiB

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,95 @@
#include <glbinding/glbinding.h>
#include <glbinding/gl/gl.h>
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>
#include <iostream>
#include <spine-glfw.h>
int width = 800, height = 600;
GLFWwindow* init_glfw() {
if (!glfwInit()) {
std::cerr << "Failed to initialize GLFW" << std::endl;
return nullptr;
}
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
GLFWwindow* window = glfwCreateWindow(width, height, "spine-glfw", NULL, NULL);
if (!window) {
std::cerr << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return nullptr;
}
glfwMakeContextCurrent(window);
glbinding::initialize(glfwGetProcAddress);
return window;
}
int main() {
// Initialize GLFW and glbinding
GLFWwindow *window = init_glfw();
if (!window) return -1;
// We use a y-down coordinate system, see renderer_set_viewport_size()
spine_bone_set_is_y_down(true);
// Load the atlas and the skeleton data
atlas_t *atlas = atlas_load("/Users/badlogic/workspaces/spine-runtimes/examples/spineboy/export/spineboy.atlas");
spine_skeleton_data skeleton_data = skeleton_data_load("/Users/badlogic/workspaces/spine-runtimes/examples/spineboy/export/spineboy-pro.json", atlas);
// Create a skeleton drawable from the data, set the skeleton's position to the bottom center of
// the screen and scale it to make it smaller.
spine_skeleton_drawable drawable = spine_skeleton_drawable_create(skeleton_data);
spine_skeleton skeleton = spine_skeleton_drawable_get_skeleton(drawable);
spine_skeleton_set_position(skeleton, width / 2, height - 100);
spine_skeleton_set_scale(skeleton, 0.3, 0.3);
// Set the "portal" animation on track 0 of the animation state, looping
spine_animation_state animation_state = spine_skeleton_drawable_get_animation_state(drawable);
spine_animation_state_set_animation_by_name(animation_state, 0, "portal", true);
// Create the renderer and set the viewport size to match the window size. This sets up a
// pixel perfect orthogonal projection for 2D rendering.
renderer_t *renderer = renderer_create();
renderer_set_viewport_size(renderer, width, height);
// Rendering loop
double lastTime = glfwGetTime();
while (!glfwWindowShouldClose(window)) {
// Calculate the delta time in seconds
double currTime = glfwGetTime();
float delta = currTime - lastTime;
lastTime = currTime;
// Update and apply the animation state to the skeleton
spine_animation_state_update(animation_state, delta);
spine_animation_state_apply(animation_state, skeleton);
// Update the skeleton time (used for physics)
spine_skeleton_update(skeleton, delta);
// Calculate the new pose
spine_skeleton_update_world_transform(skeleton, SPINE_PHYSICS_UPDATE);
// Clear the screen
gl::glClear(gl::GL_COLOR_BUFFER_BIT);
// Render the skeleton in its current pose
renderer_draw(renderer, drawable, atlas);
// Present the rendering results and poll for events
glfwSwapBuffers(window);
glfwPollEvents();
}
// Dispose everything
renderer_dispose(renderer);
spine_skeleton_drawable_dispose(drawable);
spine_skeleton_data_dispose(skeleton_data);
atlas_dispose(atlas);
// Kill the window and GLFW
glfwTerminate();
return 0;
}

View File

@ -0,0 +1,416 @@
#include "spine-glfw.h"
#include <stdio.h>
#include <glbinding/gl/gl.h>
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
using namespace gl;
/// A blend mode, see https://en.esotericsoftware.com/spine-slots#Blending
/// Encodes the OpenGL source and destination blend function for both premultiplied and
/// non-premultiplied alpha blending.
typedef struct {
unsigned int source_color;
unsigned int source_color_pma;
unsigned int dest_color;
unsigned int source_alpha;
} blend_mode_t;
/// The 4 supported blend modes SPINE_BLEND_MODE_NORMAL, SPINE_BLEND_MODE_ADDITIVE, SPINE_BLEND_MODE_MULTIPLY,
/// and SPINE_BLEND_MODE_SCREEN, expressed as OpenGL blend functions.
blend_mode_t blend_modes[] = {
{(unsigned int)GL_SRC_ALPHA, (unsigned int)GL_ONE, (unsigned int)GL_ONE_MINUS_SRC_ALPHA, (unsigned int)GL_ONE},
{(unsigned int)GL_SRC_ALPHA, (unsigned int)GL_ONE, (unsigned int)GL_ONE, (unsigned int)GL_ONE},
{(unsigned int)GL_DST_COLOR, (unsigned int)GL_DST_COLOR, (unsigned int)GL_ONE_MINUS_SRC_ALPHA, (unsigned int)GL_ONE_MINUS_SRC_ALPHA},
{(unsigned int)GL_ONE, (unsigned int)GL_ONE, (unsigned int)GL_ONE_MINUS_SRC_COLOR, (unsigned int)GL_ONE_MINUS_SRC_COLOR}
};
mesh_t *mesh_create() {
GLuint vao, vbo, ibo;
glGenVertexArrays(1, &vao);
glGenBuffers(1, &vbo);
glGenBuffers(1, &ibo);
glBindVertexArray(vao);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(vertex_t), (void*)offsetof(vertex_t, x));
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(vertex_t), (void*)offsetof(vertex_t, color));
glEnableVertexAttribArray(1);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(vertex_t), (void*)offsetof(vertex_t, u));
glEnableVertexAttribArray(2);
glVertexAttribPointer(3, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(vertex_t), (void*)offsetof(vertex_t, darkColor));
glEnableVertexAttribArray(3);
glBindVertexArray(0);
mesh_t *mesh = (mesh_t*)malloc(sizeof(mesh_t));
mesh->vao = vao;
mesh->vbo = vbo;
mesh->num_vertices = 0;
mesh->ibo = ibo;
mesh->num_indices = 0;
return mesh;
}
void mesh_update(mesh_t *mesh, vertex_t *vertices, int num_vertices, uint16_t *indices, int num_indices) {
glBindVertexArray(mesh->vao);
glBindBuffer(GL_ARRAY_BUFFER, mesh->vbo);
glBufferData(GL_ARRAY_BUFFER, num_vertices * sizeof(vertex_t), vertices, GL_STATIC_DRAW);
mesh->num_vertices = num_vertices;
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh->ibo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, num_indices * sizeof(uint16_t), indices, GL_STATIC_DRAW);
mesh->num_indices = num_indices;
glBindVertexArray(0);
}
void mesh_draw(mesh_t *mesh) {
glBindVertexArray(mesh->vao);
glDrawElements(GL_TRIANGLES, mesh->num_indices, GL_UNSIGNED_SHORT, 0);
glBindVertexArray(0);
}
void mesh_dispose(mesh_t *mesh) {
glDeleteBuffers(1, &mesh->vbo);
glDeleteBuffers(1, &mesh->ibo);
glDeleteVertexArrays(1, &mesh->vao);
free(mesh);
}
GLuint compile_shader(const char* source, GLenum type) {
GLuint shader = glCreateShader(type);
glShaderSource(shader, 1, &source, nullptr);
glCompileShader(shader);
GLint success;
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
if (!success) {
char infoLog[512];
glGetShaderInfoLog(shader, 512, nullptr, infoLog);
printf("Error, shader compilation failed:\n%s\n", infoLog);
glDeleteShader(shader);
return 0;
}
return shader;
}
shader_t shader_create(const char* vertex_shader, const char* fragment_shader) {
shader_t program;
GLuint vertex_shader_id = compile_shader(vertex_shader, GL_VERTEX_SHADER);
GLuint fragment_shader_id = compile_shader(fragment_shader, GL_FRAGMENT_SHADER);
if (!vertex_shader_id || !fragment_shader_id) {
glDeleteShader(vertex_shader_id);
glDeleteShader(fragment_shader_id);
return 0;
}
program = glCreateProgram();
glAttachShader(program, vertex_shader_id);
glAttachShader(program, fragment_shader_id);
glLinkProgram(program);
GLint success;
glGetProgramiv(program, GL_LINK_STATUS, &success);
if (!success) {
char infoLog[512];
glGetProgramInfoLog(program, 512, nullptr, infoLog);
printf("Error, shader linking failed:\n%s\n", infoLog);
glDeleteProgram(program);
program = 0;
}
glDeleteShader(vertex_shader_id);
glDeleteShader(fragment_shader_id);
return program;
}
void shader_set_matrix4(shader_t shader, const char* name, const float *matrix) {
shader_use(shader);
GLint location = glGetUniformLocation(shader, name);
glUniformMatrix4fv(location, 1, GL_FALSE, matrix);
}
void shader_set_float(shader_t shader, const char* name, float value) {
shader_use(shader);
GLint location = glGetUniformLocation(shader, name);
glUniform1f(location, value);
}
void shader_set_int(shader_t shader, const char* name, int value) {
shader_use(shader);
GLint location = glGetUniformLocation(shader, name);
glUniform1i(location, value);
}
void shader_use(shader_t program) {
glUseProgram(program);
}
void shader_dispose(shader_t program) {
glDeleteProgram(program);
}
texture_t texture_load(const char *file_path) {
int width, height, nrChannels;
unsigned char *data = stbi_load(file_path, &width, &height, &nrChannels, 0);
if (!data) {
printf("Failed to load texture\n");
return 0;
}
GLenum format = GL_RGBA;
if (nrChannels == 1)
format = GL_RED;
else if (nrChannels == 3)
format = GL_RGB;
else if (nrChannels == 4)
format = GL_RGBA;
texture_t texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);
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, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
stbi_image_free(data);
return texture;
}
void texture_use(texture_t texture) {
glActiveTexture(GL_TEXTURE0); // Set active texture unit to 0
glBindTexture(GL_TEXTURE_2D, texture);
}
void texture_dispose(texture_t texture) {
glDeleteTextures(1, &texture);
}
void matrix_ortho_projection(float *matrix, float width, float height) {
std::memset(matrix, 0, 16 * sizeof(float));
float left = 0.0f;
float right = width;
float bottom = height;
float top = 0.0f;
float near = -1.0f;
float far = 1.0f;
matrix[0] = 2.0f / (right - left);
matrix[5] = 2.0f / (top - bottom);
matrix[10] = -2.0f / (far - near);
matrix[12] = -(right + left) / (right - left);
matrix[13] = -(top + bottom) / (top - bottom);
matrix[14] = -(far + near) / (far - near);
matrix[15] = 1.0f;
}
const uint8_t *file_read(const char *path, int *length) {
uint8_t *data;
FILE *file = fopen(path, "rb");
if (!file) return 0;
fseek(file, 0, SEEK_END);
*length = (int) ftell(file);
fseek(file, 0, SEEK_SET);
data = (uint8_t*)(malloc(*length + 1));
fread(data, 1, *length, file);
fclose(file);
data[*length] = 0;
return data;
}
atlas_t *atlas_load(const char *file_path) {
int length = 0;
utf8 *atlas_data = (utf8*)file_read(file_path, &length);
if (!atlas_data) {
printf("Could not load atlas %s\n", file_path);
return nullptr;
}
spine_atlas spine_atlas = spine_atlas_load(atlas_data);
free(atlas_data);
if (!spine_atlas) {
printf("Could not load atlas %s\n", file_path);
return nullptr;
}
atlas_t *atlas = (atlas_t*)malloc(sizeof(atlas_t));
atlas->atlas = spine_atlas;
int num_textures = spine_atlas_get_num_image_paths(spine_atlas);
atlas->textures = (texture_t*)malloc(sizeof(texture_t) * num_textures);
memset(atlas->textures, 0, sizeof(texture_t) * num_textures);
char parent_dir[1024];
strncpy(parent_dir, file_path, sizeof(parent_dir));
char *last_slash = strrchr(parent_dir, '/');
if (last_slash) {
*(last_slash + 1) = '\0';
} else {
parent_dir[0] = '\0';
}
for (int i = 0; i < num_textures; i++) {
char *relative_path = spine_atlas_get_image_path(spine_atlas, i);
char full_path[1024];
snprintf(full_path, sizeof(full_path), "%s%s", parent_dir, relative_path);
texture_t texture = texture_load(full_path);
if (!texture) {
printf("Could not load atlas texture %s\n", full_path);
atlas_dispose(atlas);
return nullptr;
}
atlas->textures[i] = texture;
}
return atlas;
}
void atlas_dispose(atlas_t *atlas) {
for (int i = 0; i < spine_atlas_get_num_image_paths(atlas->atlas); i++) {
texture_dispose(atlas->textures[i]);
}
spine_atlas_dispose(atlas->atlas);
free(atlas->textures);
free(atlas);
}
spine_skeleton_data skeleton_data_load(const char *file_path, atlas_t *atlas) {
int length = 0;
uint8_t *data = (uint8_t*)file_read(file_path, &length);
if (!data) {
printf("Could not load skeleton data file %s\n", file_path);
return nullptr;
}
spine_skeleton_data_result result;
const char *ext = strrchr(file_path, '.');
if (ext && strcmp(ext, ".skel") == 0) {
result = spine_skeleton_data_load_binary(atlas->atlas, data, length);
} else {
result = spine_skeleton_data_load_json(atlas->atlas, (utf8*)data);
}
free(data);
if (spine_skeleton_data_result_get_error(result)) {
printf("Could not load skeleton data file %s:\n%s\n", file_path, spine_skeleton_data_result_get_error(result));
spine_skeleton_data_result_dispose(result);
return nullptr;
}
spine_skeleton_data skeleton_data = spine_skeleton_data_result_get_data(result);
spine_skeleton_data_result_dispose(result);
return skeleton_data;
}
renderer_t *renderer_create() {
shader_t shader = shader_create(R"(
#version 330 core
layout (location = 0) in vec2 aPos;
layout (location = 1) in vec4 aLightColor;
layout (location = 2) in vec2 aTexCoord;
layout (location = 3) in vec4 aDarkColor;
uniform mat4 uMatrix;
out vec4 lightColor;
out vec4 darkColor;
out vec2 texCoord;
void main() {
lightColor = aLightColor;
darkColor = aDarkColor;
texCoord = aTexCoord;
gl_Position = uMatrix * vec4(aPos, 0.0, 1.0);
}
)", R"(
#version 330 core
in vec4 lightColor;
in vec4 darkColor;
in vec2 texCoord;
out vec4 fragColor;
uniform sampler2D uTexture;
void main() {
vec4 texColor = texture(uTexture, texCoord);
float alpha = texColor.a * lightColor.a;
fragColor.a = alpha;
fragColor.rgb = ((texColor.a - 1.0) * darkColor.a + 1.0 - texColor.rgb) * darkColor.rgb + texColor.rgb * lightColor.rgb;
}
)");
if (!shader) return nullptr;
mesh_t *mesh = mesh_create();
renderer_t *renderer = (renderer_t*)malloc(sizeof(renderer_t));
renderer->shader = shader;
renderer->mesh = mesh;
renderer->vertex_buffer_size = 0;
renderer->vertex_buffer = nullptr;
return renderer;
}
void renderer_set_viewport_size(renderer_t *renderer, int width, int height) {
float matrix[16];
matrix_ortho_projection(matrix, width, height);
shader_use(renderer->shader);
shader_set_matrix4(renderer->shader, "uMatrix", matrix);
}
void renderer_draw(renderer_t *renderer, spine_skeleton_drawable drawable, atlas_t *atlas) {
shader_use(renderer->shader);
shader_set_int(renderer->shader, "uTexture", 0);
gl::glEnable(gl::GLenum::GL_BLEND);
spine_render_command command = spine_skeleton_drawable_render(drawable);
while (command) {
int num_command_vertices = spine_render_command_get_num_vertices(command);
if (renderer->vertex_buffer_size < num_command_vertices) {
renderer->vertex_buffer_size = num_command_vertices;
free(renderer->vertex_buffer);
renderer->vertex_buffer = (vertex_t *)malloc(sizeof(vertex_t) * renderer->vertex_buffer_size);
}
float *positions = spine_render_command_get_positions(command);
float *uvs = spine_render_command_get_uvs(command);
int32_t *colors = spine_render_command_get_colors(command);
int32_t *darkColors = spine_render_command_get_dark_colors(command);
for (int i = 0, j = 0; i < num_command_vertices; i++, j += 2) {
vertex_t *vertex = &renderer->vertex_buffer[i];
vertex->x = positions[j];
vertex->y = positions[j + 1];
vertex->u = uvs[j];
vertex->v = uvs[j+1];
uint32_t color = colors[i];
vertex->color = (color & 0xFF00FF00) | ((color & 0x00FF0000) >> 16) | ((color & 0x000000FF) << 16);
uint32_t darkColor = darkColors[i];
vertex->darkColor = (darkColor & 0xFF00FF00) | ((darkColor & 0x00FF0000) >> 16) | ((darkColor & 0x000000FF) << 16);
}
int num_command_indices = spine_render_command_get_num_indices(command);
uint16_t *indices = spine_render_command_get_indices(command);
mesh_update(renderer->mesh, renderer->vertex_buffer, num_command_vertices, indices, num_command_indices);
blend_mode_t blend_mode = blend_modes[spine_render_command_get_blend_mode(command)];
gl::glBlendFuncSeparate(spine_atlas_is_pma(atlas->atlas) ? (gl::GLenum)blend_mode.source_color_pma : (gl::GLenum)blend_mode.source_color, (gl::GLenum)blend_mode.dest_color, (gl::GLenum)blend_mode.source_alpha, (gl::GLenum)blend_mode.dest_color);
texture_t texture = atlas->textures[spine_render_command_get_atlas_page(command)];
texture_use(texture);
mesh_draw(renderer->mesh);
command = spine_render_command_get_next(command);
}
}
void renderer_dispose(renderer_t *renderer) {
shader_dispose(renderer->shader);
mesh_dispose(renderer->mesh);
free(renderer->vertex_buffer);
free(renderer);
}

View File

@ -0,0 +1,98 @@
#pragma once
#include <stdint.h>
#include <spine-cpp-lite.h>
/// A vertex of a mesh generated from a Spine skeleton
struct vertex_t {
float x, y;
uint32_t color;
float u, v;
uint32_t darkColor;
};
/// A GPU-side mesh using OpenGL vertex arrays, vertex buffer, and
/// indices buffer.
typedef struct {
unsigned int vao;
unsigned int vbo;
int num_vertices;
unsigned int ibo;
int num_indices;
} mesh_t;
mesh_t *mesh_create();
void mesh_update(mesh_t *mesh, vertex_t *vertices, int num_vertices, uint16_t *indices, int num_indices);
void mesh_draw(mesh_t *mesh);
void mesh_dispose(mesh_t *mesh);
/// A shader (the OpenGL shader program id)
typedef unsigned int shader_t;
/// Creates a shader program from the vertex and fragment shader
shader_t shader_create(const char *vertex_shader, const char *fragment_shader);
/// Sets a uniform matrix by name
void shader_set_matrix4(shader_t program, const char* name, const float *matrix);
/// Sets a uniform float by name
void shader_set_float(shader_t program, const char* name, float value);
/// Sets a uniform int by name
void shader_set_int(shader_t program, const char* name, int value);
/// Binds the shader
void shader_use(shader_t shader);
/// Disposes the shader
void shader_dispose(shader_t shader);
/// A texture (the OpenGL texture object id)
typedef unsigned int texture_t;
/// Loads the given image and creates an OpenGL texture with default settings and auto-generated mipmap levels
texture_t texture_load(const char *file_path);
/// Binds the texture to texture unit 0
void texture_use(texture_t texture);
/// Disposes the texture
void texture_dispose(texture_t texture);
/// Helper struct that contains a Spine atlas and the textures for each
/// atlas page
typedef struct {
spine_atlas atlas;
texture_t *textures;
} atlas_t;
/// Loads the .atlas file and its associated atlas pages as OpenGL textures
atlas_t *atlas_load(const char *file_path);
/// Disposes the atlas data and its associated OpenGL textures
void atlas_dispose(atlas_t *atlas);
/// Loads the skeleton data from the .skel or .json file using the given atlas
spine_skeleton_data skeleton_data_load(const char *file_path, atlas_t *atlas);
/// Renderer capable of rendering a spine_skeleton_drawable, using a shader, a mesh, and a
/// temporary CPU-side vertex buffer used to update the GPU-side mesh
typedef struct {
shader_t shader;
mesh_t *mesh;
int vertex_buffer_size;
vertex_t *vertex_buffer;
} renderer_t;
/// Creates a new renderer
renderer_t *renderer_create();
/// Sets the viewport size for the 2D orthographic projection
void renderer_set_viewport_size(renderer_t *renderer, int width, int height);
/// Draws the given skeleton drawbale. The atlas must be the atlas from which the drawable
/// was constructed.
void renderer_draw(renderer_t *renderer, spine_skeleton_drawable drawable, atlas_t *atlas);
/// Disposes the renderer
void renderer_dispose(renderer_t *renderer);

7979
spine-glfw/src/stb_image.h Normal file

File diff suppressed because it is too large Load Diff