mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2025-12-21 01:36:02 +08:00
[glfw] Rewrite using new SkeletonRenderer and switch to spine-cpp.
This commit is contained in:
parent
072d5f1539
commit
9db5f90ccb
@ -81,6 +81,7 @@ public:
|
||||
}
|
||||
|
||||
void compress() {
|
||||
if (blocks.size() == 1) return;
|
||||
int totalSize = 0;
|
||||
for (int i = 0, n = blocks.size(); i < n; i++) {
|
||||
totalSize += blocks[i].size;
|
||||
|
||||
110
spine-cpp/spine-cpp/include/spine/BlockAllocator.h
Normal file
110
spine-cpp/spine-cpp/include/spine/BlockAllocator.h
Normal file
@ -0,0 +1,110 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated July 28, 2023. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2023, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software or
|
||||
* otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
|
||||
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
#ifndef Spine_BlockAllocator_h
|
||||
#define Spine_BlockAllocator_h
|
||||
|
||||
#include <cstdint>
|
||||
#include <spine/SpineObject.h>
|
||||
#include <spine/Extension.h>
|
||||
#include <spine/MathUtil.h>
|
||||
#include <spine/Vector.h>
|
||||
|
||||
namespace spine {
|
||||
struct Block {
|
||||
int size;
|
||||
int allocated;
|
||||
uint8_t *memory;
|
||||
|
||||
int free() {
|
||||
return size - allocated;
|
||||
}
|
||||
|
||||
bool canFit(int numBytes) {
|
||||
return free() >= numBytes;
|
||||
}
|
||||
|
||||
uint8_t *allocate(int numBytes) {
|
||||
uint8_t *ptr = memory + allocated;
|
||||
allocated += numBytes;
|
||||
return ptr;
|
||||
}
|
||||
};
|
||||
|
||||
class BlockAllocator : public SpineObject {
|
||||
int initialBlockSize;
|
||||
Vector <Block> blocks;
|
||||
|
||||
public:
|
||||
BlockAllocator(int initialBlockSize) : initialBlockSize(initialBlockSize) {
|
||||
blocks.add(newBlock(initialBlockSize));
|
||||
}
|
||||
|
||||
~BlockAllocator() {
|
||||
for (int i = 0, n = (int) blocks.size(); i < n; i++) {
|
||||
SpineExtension::free(blocks[i].memory, __FILE__, __LINE__);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T *allocate(size_t num) {
|
||||
return (T *) _allocate((int) (sizeof(T) * num));
|
||||
}
|
||||
|
||||
void compress() {
|
||||
if (blocks.size() == 1) return;
|
||||
int totalSize = 0;
|
||||
for (int i = 0, n = blocks.size(); i < n; i++) {
|
||||
totalSize += blocks[i].size;
|
||||
SpineExtension::free(blocks[i].memory, __FILE__, __LINE__);
|
||||
}
|
||||
blocks.clear();
|
||||
blocks.add(newBlock(totalSize));
|
||||
}
|
||||
|
||||
private:
|
||||
void *_allocate(int numBytes) {
|
||||
// 16-byte align allocations
|
||||
int alignedNumBytes = numBytes + (numBytes % 16 != 0 ? 16 - (numBytes % 16) : 0);
|
||||
Block *block = &blocks[blocks.size() - 1];
|
||||
if (!block->canFit(alignedNumBytes)) {
|
||||
blocks.add(newBlock(MathUtil::max(initialBlockSize, alignedNumBytes)));
|
||||
block = &blocks[blocks.size() - 1];
|
||||
}
|
||||
return block->allocate(alignedNumBytes);
|
||||
}
|
||||
|
||||
Block newBlock(int numBytes) {
|
||||
Block block = {MathUtil::max(initialBlockSize, numBytes), 0, nullptr};
|
||||
block.memory = SpineExtension::alloc<uint8_t>(block.size, __FILE__, __LINE__);
|
||||
return block;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
68
spine-cpp/spine-cpp/include/spine/SkeletonRenderer.h
Normal file
68
spine-cpp/spine-cpp/include/spine/SkeletonRenderer.h
Normal file
@ -0,0 +1,68 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated July 28, 2023. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2023, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software or
|
||||
* otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
|
||||
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
#ifndef Spine_SkeletonRenderer_h
|
||||
#define Spine_SkeletonRenderer_h
|
||||
|
||||
#include <spine/BlockAllocator.h>
|
||||
#include <spine/BlendMode.h>
|
||||
#include <spine/SkeletonClipping.h>
|
||||
|
||||
namespace spine {
|
||||
class Skeleton;
|
||||
|
||||
struct SP_API RenderCommand {
|
||||
float *positions;
|
||||
float *uvs;
|
||||
int32_t *colors;
|
||||
int32_t *darkColors;
|
||||
int32_t numVertices;
|
||||
uint16_t *indices;
|
||||
int32_t numIndices;
|
||||
BlendMode blendMode;
|
||||
void *texture;
|
||||
RenderCommand *next;
|
||||
};
|
||||
|
||||
class SP_API SkeletonRenderer: public SpineObject {
|
||||
public:
|
||||
explicit SkeletonRenderer();
|
||||
|
||||
~SkeletonRenderer();
|
||||
|
||||
RenderCommand *render(Skeleton &skeleton);
|
||||
private:
|
||||
BlockAllocator _allocator;
|
||||
Vector<float> _worldVertices;
|
||||
Vector<unsigned short> _quadIndices;
|
||||
SkeletonClipping _clipping;
|
||||
Vector<RenderCommand *> _renderCommands;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@ -93,6 +93,7 @@
|
||||
#include <spine/SkeletonClipping.h>
|
||||
#include <spine/SkeletonData.h>
|
||||
#include <spine/SkeletonJson.h>
|
||||
#include <spine/SkeletonRenderer.h>
|
||||
#include <spine/Skin.h>
|
||||
#include <spine/Slot.h>
|
||||
#include <spine/SlotData.h>
|
||||
|
||||
248
spine-cpp/spine-cpp/src/spine/SkeletonRenderer.cpp
Normal file
248
spine-cpp/spine-cpp/src/spine/SkeletonRenderer.cpp
Normal file
@ -0,0 +1,248 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated July 28, 2023. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2023, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software or
|
||||
* otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
|
||||
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
#include <spine/SkeletonRenderer.h>
|
||||
#include <spine/Skeleton.h>
|
||||
#include <spine/Slot.h>
|
||||
#include <spine/SlotData.h>
|
||||
#include <spine/RegionAttachment.h>
|
||||
#include <spine/Atlas.h>
|
||||
#include <spine/MeshAttachment.h>
|
||||
#include <spine/ClippingAttachment.h>
|
||||
#include <spine/Bone.h>
|
||||
|
||||
using namespace spine;
|
||||
|
||||
SkeletonRenderer::SkeletonRenderer(): _allocator(4096), _worldVertices(), _quadIndices(), _clipping(), _renderCommands() {
|
||||
_quadIndices.add(0);
|
||||
_quadIndices.add(1);
|
||||
_quadIndices.add(2);
|
||||
_quadIndices.add(2);
|
||||
_quadIndices.add(3);
|
||||
_quadIndices.add(0);
|
||||
}
|
||||
|
||||
SkeletonRenderer::~SkeletonRenderer() {
|
||||
}
|
||||
|
||||
static RenderCommand *createRenderCommand(BlockAllocator &allocator, int numVertices, int32_t numIndices, BlendMode blendMode, void *texture) {
|
||||
RenderCommand *cmd = allocator.allocate<RenderCommand>(1);
|
||||
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;
|
||||
cmd->blendMode = blendMode;
|
||||
cmd->texture = texture;
|
||||
cmd->next = nullptr;
|
||||
return cmd;
|
||||
}
|
||||
|
||||
static RenderCommand *batchSubCommands(BlockAllocator &allocator, Vector<RenderCommand *> &commands, int first, int last, int numVertices, int numIndices) {
|
||||
RenderCommand *batched = createRenderCommand(allocator, numVertices, numIndices, commands[first]->blendMode, commands[first]->texture);
|
||||
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++) {
|
||||
RenderCommand *cmd = commands[i];
|
||||
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;
|
||||
}
|
||||
|
||||
static RenderCommand *batchCommands(BlockAllocator &allocator, Vector<RenderCommand *> &commands) {
|
||||
if (commands.size() == 0) return nullptr;
|
||||
|
||||
RenderCommand *root = nullptr;
|
||||
RenderCommand *last = nullptr;
|
||||
|
||||
RenderCommand *first = commands[0];
|
||||
int startIndex = 0;
|
||||
int i = 1;
|
||||
int numVertices = first->numVertices;
|
||||
int numIndices = first->numIndices;
|
||||
while (i <= (int) commands.size()) {
|
||||
RenderCommand *cmd = i < (int) commands.size() ? commands[i] : nullptr;
|
||||
|
||||
if (cmd && cmd->numVertices == 0 && cmd->numIndices == 0) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cmd != nullptr && cmd->texture == first->texture &&
|
||||
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;
|
||||
} else {
|
||||
RenderCommand *batched = batchSubCommands(allocator, commands, startIndex, i - 1, numVertices, numIndices);
|
||||
if (!last) {
|
||||
root = last = batched;
|
||||
} else {
|
||||
last->next = batched;
|
||||
last = batched;
|
||||
}
|
||||
if (i == (int) commands.size()) break;
|
||||
first = commands[i];
|
||||
startIndex = i;
|
||||
numVertices = first->numVertices;
|
||||
numIndices = first->numIndices;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
|
||||
RenderCommand *SkeletonRenderer::render(Skeleton &skeleton) {
|
||||
_allocator.compress();
|
||||
_renderCommands.clear();
|
||||
|
||||
SkeletonClipping &clipper = _clipping;
|
||||
|
||||
for (unsigned i = 0; i < skeleton.getSlots().size(); ++i) {
|
||||
Slot &slot = *skeleton.getDrawOrder()[i];
|
||||
Attachment *attachment = slot.getAttachment();
|
||||
if (!attachment) {
|
||||
clipper.clipEnd(slot);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Early out if the slot color is 0 or the bone is not active
|
||||
if (slot.getColor().a == 0 || !slot.getBone().isActive()) {
|
||||
clipper.clipEnd(slot);
|
||||
continue;
|
||||
}
|
||||
|
||||
Vector<float> *worldVertices = &_worldVertices;
|
||||
Vector<unsigned short> *quadIndices = &_quadIndices;
|
||||
Vector<float> *vertices = worldVertices;
|
||||
int32_t verticesCount;
|
||||
Vector<float> *uvs;
|
||||
Vector<unsigned short> *indices;
|
||||
int32_t indicesCount;
|
||||
Color *attachmentColor;
|
||||
void *texture;
|
||||
|
||||
if (attachment->getRTTI().isExactly(RegionAttachment::rtti)) {
|
||||
RegionAttachment *regionAttachment = (RegionAttachment *) attachment;
|
||||
attachmentColor = ®ionAttachment->getColor();
|
||||
|
||||
// Early out if the slot color is 0
|
||||
if (attachmentColor->a == 0) {
|
||||
clipper.clipEnd(slot);
|
||||
continue;
|
||||
}
|
||||
|
||||
worldVertices->setSize(8, 0);
|
||||
regionAttachment->computeWorldVertices(slot, *worldVertices, 0, 2);
|
||||
verticesCount = 4;
|
||||
uvs = ®ionAttachment->getUVs();
|
||||
indices = quadIndices;
|
||||
indicesCount = 6;
|
||||
texture = regionAttachment->getRegion()->rendererObject;
|
||||
|
||||
} else if (attachment->getRTTI().isExactly(MeshAttachment::rtti)) {
|
||||
MeshAttachment *mesh = (MeshAttachment *) attachment;
|
||||
attachmentColor = &mesh->getColor();
|
||||
|
||||
// Early out if the slot color is 0
|
||||
if (attachmentColor->a == 0) {
|
||||
clipper.clipEnd(slot);
|
||||
continue;
|
||||
}
|
||||
|
||||
worldVertices->setSize(mesh->getWorldVerticesLength(), 0);
|
||||
mesh->computeWorldVertices(slot, 0, mesh->getWorldVerticesLength(), worldVertices->buffer(), 0, 2);
|
||||
verticesCount = (int32_t) (mesh->getWorldVerticesLength() >> 1);
|
||||
uvs = &mesh->getUVs();
|
||||
indices = &mesh->getTriangles();
|
||||
indicesCount = (int32_t) indices->size();
|
||||
texture = mesh->getRegion()->rendererObject;
|
||||
|
||||
} else if (attachment->getRTTI().isExactly(ClippingAttachment::rtti)) {
|
||||
ClippingAttachment *clip = (ClippingAttachment *) slot.getAttachment();
|
||||
clipper.clipStart(slot, clip);
|
||||
continue;
|
||||
} else
|
||||
continue;
|
||||
|
||||
uint8_t r = static_cast<uint8_t>(skeleton.getColor().r * slot.getColor().r * attachmentColor->r * 255);
|
||||
uint8_t g = static_cast<uint8_t>(skeleton.getColor().g * slot.getColor().g * attachmentColor->g * 255);
|
||||
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);
|
||||
vertices = &clipper.getClippedVertices();
|
||||
verticesCount = (int32_t) (clipper.getClippedVertices().size() >> 1);
|
||||
uvs = &clipper.getClippedUVs();
|
||||
indices = &clipper.getClippedTriangles();
|
||||
indicesCount = (int32_t) (clipper.getClippedTriangles().size());
|
||||
}
|
||||
|
||||
RenderCommand *cmd = createRenderCommand(_allocator, verticesCount, indicesCount, slot.getData().getBlendMode(), texture);
|
||||
_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;
|
||||
cmd->darkColors[ii] = darkColor;
|
||||
}
|
||||
memcpy(cmd->indices, indices->buffer(), indices->size() * sizeof(uint16_t));
|
||||
clipper.clipEnd(slot);
|
||||
}
|
||||
clipper.clipEnd();
|
||||
|
||||
return batchCommands(_allocator, _renderCommands);
|
||||
}
|
||||
@ -4,7 +4,7 @@ project(spine-glfw)
|
||||
# Default flags
|
||||
include(${CMAKE_SOURCE_DIR}/../flags.cmake)
|
||||
|
||||
# Add spine-cpp and spine-cpp-lite
|
||||
# Add spine-cpp
|
||||
add_subdirectory(${CMAKE_SOURCE_DIR}/../spine-cpp ${CMAKE_BINARY_DIR}/spine-cpp-build)
|
||||
|
||||
include(FetchContent)
|
||||
@ -41,7 +41,7 @@ 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)
|
||||
target_link_libraries(spine-glfw LINK_PUBLIC spine-cpp)
|
||||
|
||||
# Example
|
||||
add_executable(spine-glfw-example example/main.cpp)
|
||||
|
||||
@ -5,6 +5,8 @@
|
||||
#include <iostream>
|
||||
#include <spine-glfw.h>
|
||||
|
||||
using namespace spine;
|
||||
|
||||
int width = 800, height = 600;
|
||||
|
||||
GLFWwindow* init_glfw() {
|
||||
@ -32,22 +34,26 @@ int main() {
|
||||
if (!window) return -1;
|
||||
|
||||
// We use a y-down coordinate system, see renderer_set_viewport_size()
|
||||
spine_bone_set_is_y_down(true);
|
||||
Bone::setYDown(true);
|
||||
|
||||
// Load the atlas and the skeleton data
|
||||
atlas_t *atlas = atlas_load("data/spineboy-pma.atlas");
|
||||
spine_skeleton_data skeleton_data = skeleton_data_load("data/spineboy-pro.json", atlas);
|
||||
GlTextureLoader textureLoader;
|
||||
Atlas *atlas = new Atlas("data/spineboy-pma.atlas", &textureLoader);
|
||||
SkeletonJson json(atlas);
|
||||
SkeletonData *skeletonData = json.readSkeletonDataFile("data/spineboy-pro.json");
|
||||
|
||||
// Create a skeleton drawable from the data, set the skeleton's position to the bottom center of
|
||||
// Create a skeleton 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);
|
||||
Skeleton skeleton(skeletonData);
|
||||
skeleton.setPosition(width / 2, height - 100);
|
||||
skeleton.setScaleX(0.3);
|
||||
skeleton.setScaleY(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 an AnimationState to drive animations on the skeleton. Set the "portal" animation
|
||||
// on track with index 0.
|
||||
AnimationStateData animationStateData(skeletonData);
|
||||
AnimationState animationState(&animationStateData);
|
||||
animationState.setAnimation(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.
|
||||
@ -63,20 +69,20 @@ int main() {
|
||||
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);
|
||||
animationState.update(delta);
|
||||
animationState.apply(skeleton);
|
||||
|
||||
// Update the skeleton time (used for physics)
|
||||
spine_skeleton_update(skeleton, delta);
|
||||
skeleton.update(delta);
|
||||
|
||||
// Calculate the new pose
|
||||
spine_skeleton_update_world_transform(skeleton, SPINE_PHYSICS_UPDATE);
|
||||
skeleton.updateWorldTransform(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);
|
||||
renderer_draw(renderer, &skeleton, true);
|
||||
|
||||
// Present the rendering results and poll for events
|
||||
glfwSwapBuffers(window);
|
||||
@ -85,9 +91,8 @@ int main() {
|
||||
|
||||
// Dispose everything
|
||||
renderer_dispose(renderer);
|
||||
spine_skeleton_drawable_dispose(drawable);
|
||||
spine_skeleton_data_dispose(skeleton_data);
|
||||
atlas_dispose(atlas);
|
||||
delete skeletonData;
|
||||
delete atlas;
|
||||
|
||||
// Kill the window and GLFW
|
||||
glfwTerminate();
|
||||
|
||||
@ -1,10 +1,16 @@
|
||||
#include "spine-glfw.h"
|
||||
#include <stdio.h>
|
||||
#include <cstdio>
|
||||
#include <glbinding/gl/gl.h>
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include "stb_image.h"
|
||||
|
||||
using namespace gl;
|
||||
using namespace spine;
|
||||
|
||||
/// Set the default extension used for memory allocations and file I/O
|
||||
SpineExtension *spine::getDefaultExtension() {
|
||||
return new spine::DefaultSpineExtension();
|
||||
}
|
||||
|
||||
/// A blend mode, see https://en.esotericsoftware.com/spine-slots#Blending
|
||||
/// Encodes the OpenGL source and destination blend function for both premultiplied and
|
||||
@ -50,7 +56,7 @@ mesh_t *mesh_create() {
|
||||
|
||||
glBindVertexArray(0);
|
||||
|
||||
mesh_t *mesh = (mesh_t*)malloc(sizeof(mesh_t));
|
||||
auto *mesh = (mesh_t*)malloc(sizeof(mesh_t));
|
||||
mesh->vao = vao;
|
||||
mesh->vbo = vbo;
|
||||
mesh->num_vertices = 0;
|
||||
@ -63,10 +69,10 @@ void mesh_update(mesh_t *mesh, vertex_t *vertices, int num_vertices, uint16_t *i
|
||||
glBindVertexArray(mesh->vao);
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, mesh->vbo);
|
||||
glBufferData(GL_ARRAY_BUFFER, num_vertices * sizeof(vertex_t), vertices, GL_STATIC_DRAW);
|
||||
glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(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);
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, (GLsizeiptr)(num_indices * sizeof(uint16_t)), indices, GL_STATIC_DRAW);
|
||||
mesh->num_indices = num_indices;
|
||||
|
||||
glBindVertexArray(0);
|
||||
@ -74,7 +80,7 @@ void mesh_update(mesh_t *mesh, vertex_t *vertices, int num_vertices, uint16_t *i
|
||||
|
||||
void mesh_draw(mesh_t *mesh) {
|
||||
glBindVertexArray(mesh->vao);
|
||||
glDrawElements(GL_TRIANGLES, mesh->num_indices, GL_UNSIGNED_SHORT, 0);
|
||||
glDrawElements(GL_TRIANGLES, mesh->num_indices, GL_UNSIGNED_SHORT, nullptr);
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
|
||||
@ -218,99 +224,12 @@ void matrix_ortho_projection(float *matrix, float width, float height) {
|
||||
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;
|
||||
void GlTextureLoader::load(spine::AtlasPage &page, const spine::String &path) {
|
||||
page.texture = (void *)(uintptr_t)texture_load(path.buffer());
|
||||
}
|
||||
|
||||
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;
|
||||
void GlTextureLoader::unload(void *texture) {
|
||||
texture_dispose((texture_t)(uintptr_t)texture);
|
||||
}
|
||||
|
||||
renderer_t *renderer_create() {
|
||||
@ -350,38 +269,39 @@ renderer_t *renderer_create() {
|
||||
)");
|
||||
if (!shader) return nullptr;
|
||||
mesh_t *mesh = mesh_create();
|
||||
renderer_t *renderer = (renderer_t*)malloc(sizeof(renderer_t));
|
||||
auto *renderer = (renderer_t*)malloc(sizeof(renderer_t));
|
||||
renderer->shader = shader;
|
||||
renderer->mesh = mesh;
|
||||
renderer->vertex_buffer_size = 0;
|
||||
renderer->vertex_buffer = nullptr;
|
||||
renderer->renderer = new SkeletonRenderer();
|
||||
return renderer;
|
||||
}
|
||||
|
||||
void renderer_set_viewport_size(renderer_t *renderer, int width, int height) {
|
||||
float matrix[16];
|
||||
matrix_ortho_projection(matrix, width, height);
|
||||
matrix_ortho_projection(matrix, (float)width, (float)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) {
|
||||
void renderer_draw(renderer_t *renderer, Skeleton *skeleton, bool premultipliedAlpha) {
|
||||
shader_use(renderer->shader);
|
||||
shader_set_int(renderer->shader, "uTexture", 0);
|
||||
gl::glEnable(gl::GLenum::GL_BLEND);
|
||||
glEnable(GL_BLEND);
|
||||
|
||||
spine_render_command command = spine_skeleton_drawable_render(drawable);
|
||||
RenderCommand *command = renderer->renderer->render(*skeleton);
|
||||
while (command) {
|
||||
int num_command_vertices = spine_render_command_get_num_vertices(command);
|
||||
int num_command_vertices = command->numVertices;
|
||||
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);
|
||||
float *positions = command->positions;
|
||||
float *uvs = command->uvs;
|
||||
int32_t *colors = command->colors;
|
||||
int32_t *darkColors = command->darkColors;
|
||||
for (int i = 0, j = 0; i < num_command_vertices; i++, j += 2) {
|
||||
vertex_t *vertex = &renderer->vertex_buffer[i];
|
||||
vertex->x = positions[j];
|
||||
@ -393,18 +313,18 @@ void renderer_draw(renderer_t *renderer, spine_skeleton_drawable drawable, atlas
|
||||
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);
|
||||
int num_command_indices = command->numIndices;
|
||||
uint16_t *indices = command->indices;
|
||||
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);
|
||||
blend_mode_t blend_mode = blend_modes[command->blendMode];
|
||||
glBlendFuncSeparate(premultipliedAlpha ? (GLenum)blend_mode.source_color_pma : (GLenum)blend_mode.source_color, (GLenum)blend_mode.dest_color, (GLenum)blend_mode.source_alpha, (GLenum)blend_mode.dest_color);
|
||||
|
||||
texture_t texture = atlas->textures[spine_render_command_get_atlas_page(command)];
|
||||
auto texture = (texture_t)(uintptr_t)command->texture;
|
||||
texture_use(texture);
|
||||
|
||||
mesh_draw(renderer->mesh);
|
||||
command = spine_render_command_get_next(command);
|
||||
command = command->next;
|
||||
}
|
||||
}
|
||||
|
||||
@ -412,5 +332,6 @@ void renderer_dispose(renderer_t *renderer) {
|
||||
shader_dispose(renderer->shader);
|
||||
mesh_dispose(renderer->mesh);
|
||||
free(renderer->vertex_buffer);
|
||||
delete renderer->renderer;
|
||||
free(renderer);
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <spine-cpp-lite.h>
|
||||
#include <spine/spine.h>
|
||||
|
||||
/// A vertex of a mesh generated from a Spine skeleton
|
||||
struct vertex_t {
|
||||
@ -59,21 +59,12 @@ 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);
|
||||
/// A TextureLoader implementation for OpenGL. Use this with spine::Atlas.
|
||||
class GlTextureLoader: public spine::TextureLoader {
|
||||
public:
|
||||
void load(spine::AtlasPage &page, const spine::String &path);
|
||||
void unload(void *texture);
|
||||
};
|
||||
|
||||
/// 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
|
||||
@ -82,6 +73,7 @@ typedef struct {
|
||||
mesh_t *mesh;
|
||||
int vertex_buffer_size;
|
||||
vertex_t *vertex_buffer;
|
||||
spine::SkeletonRenderer *renderer;
|
||||
} renderer_t;
|
||||
|
||||
/// Creates a new renderer
|
||||
@ -90,9 +82,9 @@ 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
|
||||
/// Draws the given skeleton. 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);
|
||||
void renderer_draw(renderer_t *renderer, spine::Skeleton *skeleton, bool premultipliedAlpha);
|
||||
|
||||
/// Disposes the renderer
|
||||
void renderer_dispose(renderer_t *renderer);
|
||||
Loading…
x
Reference in New Issue
Block a user