Yay, now the C++ Runtime passes all the same tests as the C Runtime!

This commit is contained in:
Stephen Gowen 2017-12-08 14:15:30 -05:00
parent c1995586aa
commit e24b061c61
12 changed files with 862 additions and 346 deletions

View File

@ -281,6 +281,7 @@
BB6017F01FD92A5B009BD546 /* SimpleTest.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SimpleTest.h; sourceTree = "<group>"; };
BB6017FC1FD92AF3009BD546 /* SpineEventMonitor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SpineEventMonitor.h; sourceTree = "<group>"; };
BB6017FD1FD92AF3009BD546 /* SpineEventMonitor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SpineEventMonitor.cpp; sourceTree = "<group>"; };
BBFB507F1FDAF6CD005B22B6 /* MemoryTest.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MemoryTest.h; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -326,6 +327,7 @@
children = (
BB6017121FD9289A009BD546 /* main.cpp */,
BB6017F01FD92A5B009BD546 /* SimpleTest.h */,
BBFB507F1FDAF6CD005B22B6 /* MemoryTest.h */,
);
path = spine_unit_test;
sourceTree = "<group>";

View File

@ -0,0 +1,444 @@
//
// MemoryTest.h
// spine_unit_test
//
// Created by Stephen Gowen on 12/8/17.
// Copyright © 2017 Noctis Games. All rights reserved.
//
#ifndef MemoryTest_h
#define MemoryTest_h
#include "SpineEventMonitor.h"
#include <spine/SkeletonJson.h>
#include <spine/SkeletonData.h>
#include <spine/Atlas.h>
#include <spine/AnimationStateData.h>
#include <spine/Skeleton.h>
#include <spine/AnimationState.h>
#include <spine/Animation.h>
#include <vector>
#include <spine/Extension.h>
#include <spine/TextureLoader.h>
#include <spine/Vector.h>
#include <spine/CurveTimeline.h>
#include <spine/VertexAttachment.h>
#include <spine/Json.h>
#include <spine/AttachmentLoader.h>
#include <spine/AtlasAttachmentLoader.h>
#include <spine/LinkedMesh.h>
#include <spine/Triangulator.h>
#include <spine/SkeletonClipping.h>
#include <spine/BoneData.h>
#include <spine/Bone.h>
#include <spine/SlotData.h>
#include <spine/Slot.h>
#include <spine/ClippingAttachment.h>
#include <new>
#include "KMemory.h" // last include
#define SPINEBOY_JSON "testdata/spineboy/spineboy-ess.json"
#define SPINEBOY_ATLAS "testdata/spineboy/spineboy.atlas"
#define MAX_RUN_TIME 6000 // equal to about 100 seconds of execution
namespace Spine
{
class MemoryTest
{
public:
class MyTextureLoader : public TextureLoader
{
virtual void load(AtlasPage& page, std::string path)
{
page.rendererObject = NULL;
page.width = 2048;
page.height = 2048;
}
virtual void unload(void* texture)
{
// TODO
}
};
//////////////////////////////////////////////////////////////////////////
// Helper methods
static SkeletonData* readSkeletonJsonData(const char* filename, Atlas* atlas)
{
Vector<Atlas*> atlasArray;
atlasArray.push_back(atlas);
SkeletonJson* skeletonJson = NEW(SkeletonJson);
new (skeletonJson) SkeletonJson(atlasArray);
assert(skeletonJson != 0);
SkeletonData* skeletonData = skeletonJson->readSkeletonDataFile(filename);
assert(skeletonData != 0);
DESTROY(SkeletonJson, skeletonJson);
return skeletonData;
}
static void loadSpineboyExample(Atlas* &atlas, SkeletonData* &skeletonData, AnimationStateData* &stateData, Skeleton* &skeleton, AnimationState* &state)
{
///////////////////////////////////////////////////////////////////////////
// Global Animation Information
static MyTextureLoader myTextureLoader;
atlas = NEW(Atlas);
new (atlas) Atlas(SPINEBOY_ATLAS, myTextureLoader);
assert(atlas != 0);
skeletonData = readSkeletonJsonData(SPINEBOY_JSON, atlas);
assert(skeletonData != 0);
stateData = NEW(AnimationStateData);
new (stateData) AnimationStateData(*skeletonData);
assert(stateData != 0);
stateData->setDefaultMix(0.2f); // force mixing
///////////////////////////////////////////////////////////////////////////
// Animation Instance
skeleton = NEW(Skeleton);
new (skeleton) Skeleton(*skeletonData);
assert(skeleton != 0);
state = NEW(AnimationState);
new (state) AnimationState(*stateData);
assert(state != 0);
}
static void disposeAll(Skeleton* skeleton, AnimationState* state, AnimationStateData* stateData, SkeletonData* skeletonData, Atlas* atlas)
{
///////////////////////////////////////////////////////////////////////////
// Dispose Instance
DESTROY(Skeleton, skeleton);
DESTROY(AnimationState, state);
///////////////////////////////////////////////////////////////////////////
// Dispose Global
DESTROY(AnimationStateData, stateData);
DESTROY(SkeletonData, skeletonData);
DESTROY(Atlas, atlas);
}
//////////////////////////////////////////////////////////////////////////
// Reproduce Memory leak as described in Issue #776
// https://github.com/EsotericSoftware/spine-runtimes/issues/776
static void reproduceIssue_776()
{
Atlas* atlas = NULL;
SkeletonData* skeletonData = NULL;
AnimationStateData* stateData = NULL;
Skeleton* skeleton = NULL;
AnimationState* state = NULL;
//////////////////////////////////////////////////////////////////////////
// Initialize Animations
loadSpineboyExample(atlas, skeletonData, stateData, skeleton, state);
///////////////////////////////////////////////////////////////////////////
// Run animation
skeleton->setToSetupPose();
InterruptMonitor eventMonitor(state);
// Interrupt the animation on this specific sequence of spEventType(s)
eventMonitor
.AddInterruptEvent(EventType_Interrupt, "jump")
.AddInterruptEvent(EventType_Start);
state->setAnimation(0, "walk", true);
state->addAnimation(0, "jump", false, 0.0f);
state->addAnimation(0, "run", true, 0.0f);
state->addAnimation(0, "jump", false, 3.0f);
state->addAnimation(0, "walk", true, 0.0f);
state->addAnimation(0, "idle", false, 1.0f);
for (int i = 0; i < MAX_RUN_TIME && eventMonitor.isAnimationPlaying(); ++i)
{
const float timeSlice = 1.0f / 60.0f;
skeleton->update(timeSlice);
state->update(timeSlice);
state->apply(*skeleton);
}
//////////////////////////////////////////////////////////////////////////
// Cleanup Animations
disposeAll(skeleton, state, stateData, skeletonData, atlas);
}
static void reproduceIssue_777()
{
Atlas* atlas = NULL;
SkeletonData* skeletonData = NULL;
AnimationStateData* stateData = NULL;
Skeleton* skeleton = NULL;
AnimationState* state = NULL;
//////////////////////////////////////////////////////////////////////////
// Initialize Animations
loadSpineboyExample(atlas, skeletonData, stateData, skeleton, state);
///////////////////////////////////////////////////////////////////////////
// Run animation
skeleton->setToSetupPose();
SpineEventMonitor eventMonitor(state);
// Set Animation and Play for 5 frames
state->setAnimation(0, "walk", true);
for (int i = 0; i < 5; ++i)
{
const float timeSlice = 1.0f / 60.0f;
skeleton->update(timeSlice);
state->update(timeSlice);
state->apply(*skeleton);
}
// Change animation twice in a row
state->setAnimation(0, "walk", false);
state->setAnimation(0, "run", false);
// run normal update
for (int i = 0; i < 5; ++i)
{
const float timeSlice = 1.0f / 60.0f;
skeleton->update(timeSlice);
state->update(timeSlice);
state->apply(*skeleton);
}
// Now we'd lose mixingFrom (the first "walk" entry we set above) and should leak
state->setAnimation(0, "run", false);
//////////////////////////////////////////////////////////////////////////
// Cleanup Animations
disposeAll(skeleton, state, stateData, skeletonData, atlas);
}
static void spineAnimStateHandler(AnimationState* state, EventType type, TrackEntry* entry, Event* event)
{
if (type == EventType_Complete)
{
state->setAnimation(0, "walk", false);
state->update(0);
state->apply(*skeleton);
}
}
static void reproduceIssue_Loop()
{
Atlas* atlas = NULL;
SkeletonData* skeletonData = NULL;
AnimationStateData* stateData = NULL;
AnimationState* state = NULL;
//////////////////////////////////////////////////////////////////////////
// Initialize Animations
loadSpineboyExample(atlas, skeletonData, stateData, skeleton, state);
///////////////////////////////////////////////////////////////////////////
if (state)
{
state->setOnAnimationEventFunc(spineAnimStateHandler);
}
state->setAnimation(0, "walk", false);
// run normal update
for (int i = 0; i < 50; ++i)
{
const float timeSlice = 1.0f / 60.0f;
skeleton->update(timeSlice);
state->update(timeSlice);
state->apply(*skeleton);
}
disposeAll(skeleton, state, stateData, skeletonData, atlas);
}
static void triangulator()
{
Triangulator* triangulator = NEW(Triangulator);
new (triangulator) Triangulator();
Vector<float> polygon;
polygon.reserve(16);
polygon.push_back(0);
polygon.push_back(0);
polygon.push_back(100);
polygon.push_back(0);
polygon.push_back(100);
polygon.push_back(100);
polygon.push_back(0);
polygon.push_back(100);
Vector<int> triangles = triangulator->triangulate(polygon);
assert(triangles.size() == 6);
assert(triangles[0] == 3);
assert(triangles[1] == 0);
assert(triangles[2] == 1);
assert(triangles[3] == 3);
assert(triangles[4] == 1);
assert(triangles[5] == 2);
Vector< Vector<float> *> polys = triangulator->decompose(polygon, triangles);
assert(polys.size() == 1);
assert(polys[0]->size() == 8);
assert(polys[0]->operator[](0) == 0);
assert(polys[0]->operator[](1) == 100);
assert(polys[0]->operator[](2) == 0);
assert(polys[0]->operator[](3) == 0);
assert(polys[0]->operator[](4) == 100);
assert(polys[0]->operator[](5) == 0);
assert(polys[0]->operator[](6) == 100);
assert(polys[0]->operator[](7) == 100);
DESTROY(Triangulator, triangulator);
}
static void skeletonClipper()
{
Atlas* atlas = NULL;
SkeletonData* skeletonData = NULL;
AnimationStateData* stateData = NULL;
Skeleton* skeleton = NULL;
AnimationState* state = NULL;
//////////////////////////////////////////////////////////////////////////
// Initialize Animations
loadSpineboyExample(atlas, skeletonData, stateData, skeleton, state);
SkeletonClipping* clipping = NEW(SkeletonClipping);
new (clipping) SkeletonClipping();
BoneData* boneData = NEW(BoneData);
new (boneData) BoneData(0, "bone", 0);
Bone* bone = NEW(Bone);
new(bone) Bone(*boneData, *skeleton, NULL);
bone->setA(1);
bone->setB(0);
bone->setC(0);
bone->setD(1);
bone->setWorldX(0);
bone->setWorldY(0);
SlotData* slotData = NEW(SlotData);
new (slotData) SlotData(0, "slot", *boneData);
Slot* slot = NEW(Slot);
new(slot) Slot(*slotData, *bone);
ClippingAttachment* clip = NEW(ClippingAttachment);
new(clip) ClippingAttachment("clipping");
clip->setEndSlot(slotData);
clip->setWorldVerticesLength(4 * 2);
Vector<float> clipVertices;
clipVertices.reserve(8);
clipVertices.setSize(8);
clip->setVertices(clipVertices);
clip->getVertices()[0] = 0;
clip->getVertices()[1] = 50;
clip->getVertices()[2] = 100;
clip->getVertices()[3] = 50;
clip->getVertices()[4] = 100;
clip->getVertices()[5] = 70;
clip->getVertices()[6] = 0;
clip->getVertices()[7] = 70;
clipping->clipStart(*slot, clip);
Vector<float> vertices;
vertices.reserve(16);
vertices.push_back(0);
vertices.push_back(0);
vertices.push_back(100);
vertices.push_back(0);
vertices.push_back(50);
vertices.push_back(150);
Vector<float> uvs;
uvs.reserve(16);
uvs.push_back(0);
uvs.push_back(0);
uvs.push_back(1);
uvs.push_back(0);
uvs.push_back(0.5f);
uvs.push_back(1);
Vector<int> indices;
indices.reserve(16);
indices.push_back(0);
indices.push_back(1);
indices.push_back(2);
clipping->clipTriangles(vertices, static_cast<int>(vertices.size()), indices, static_cast<int>(indices.size()), uvs);
float expectedVertices[8] = { 83.333328, 50.000000, 76.666664, 70.000000, 23.333334, 70.000000, 16.666672, 50.000000 };
assert(clipping->getClippedVertices().size() == 8);
for (int i = 0; i < clipping->getClippedVertices().size(); i++)
{
assert(abs(clipping->getClippedVertices()[i] - expectedVertices[i]) < 0.001);
}
float expectedUVs[8] = { 0.833333f, 0.333333, 0.766667, 0.466667, 0.233333, 0.466667, 0.166667, 0.333333 };
assert(clipping->getClippedUVs().size() == 8);
for (int i = 0; i < clipping->getClippedUVs().size(); i++)
{
assert(abs(clipping->getClippedUVs()[i] - expectedUVs[i]) < 0.001);
}
short expectedIndices[6] = { 0, 1, 2, 0, 2, 3 };
assert(clipping->getClippedTriangles().size() == 6);
for (int i = 0; i < clipping->getClippedTriangles().size(); i++)
{
assert(clipping->getClippedTriangles()[i] == expectedIndices[i]);
}
DESTROY(SlotData, slotData);
DESTROY(Slot, slot);
DESTROY(BoneData, boneData);
DESTROY(Bone, bone);
DESTROY(ClippingAttachment, clip);
DESTROY(SkeletonClipping, clipping);
//////////////////////////////////////////////////////////////////////////
// Cleanup Animations
disposeAll(skeleton, state, stateData, skeletonData, atlas);
}
static void test()
{
reproduceIssue_776();
reproduceIssue_777();
reproduceIssue_Loop();
triangulator();
skeletonClipper();
}
private:
static Skeleton* skeleton;
// ctor, copy ctor, and assignment should be private in a Singleton
MemoryTest();
MemoryTest(const MemoryTest&);
MemoryTest& operator=(const MemoryTest&);
};
}
Skeleton* MemoryTest::skeleton = NULL;
#endif /* MemoryTest_h */

View File

@ -1,6 +1,6 @@
//
// SimpleTest.h
// TestHarness
// spine_unit_test
//
// Created by Stephen Gowen on 11/9/17.
// Copyright © 2017 Noctis Games. All rights reserved.

View File

@ -19,6 +19,7 @@
#include "spine/Extension.h"
#include "SimpleTest.h"
#include "MemoryTest.h"
#include "KMemory.h" // last include
@ -101,6 +102,7 @@ int main(int argc, char* argv[])
#endif
SimpleTest::test();
MemoryTest::test();
// End Timing
time(&end_time);

View File

@ -1,338 +1,339 @@
//#include <spine/Extension.h>
//#include "MemoryTestFixture.h"
//#include "SpineEventMonitor.h"
//
//#include "KMemory.h" // last include
//
//#define SPINEBOY_JSON "testdata/spineboy/spineboy-ess.json"
//#define SPINEBOY_ATLAS "testdata/spineboy/spineboy.atlas"
//
//#define MAX_RUN_TIME 6000 // equal to about 100 seconds of execution
//
//MemoryTestFixture::~MemoryTestFixture()
//{
// finalize();
//}
//
//void MemoryTestFixture::initialize()
//{
// // on a Per- Fixture Basis, before Test execution
//}
//
//void MemoryTestFixture::finalize()
//{
// // on a Per- Fixture Basis, after all tests pass/fail
//}
//
//void MemoryTestFixture::setUp()
//{
// // Setup on Per-Test Basis
//}
//
//void MemoryTestFixture::tearDown()
//{
// // Tear Down on Per-Test Basis
//}
//
//
////////////////////////////////////////////////////////////////////////////
//// Helper methods
//static spSkeletonData* readSkeletonJsonData(const char* filename, spAtlas* atlas) {
// spSkeletonJson* json = spSkeletonJson_create(atlas);
// ASSERT(json != 0);
//
// spSkeletonData* skeletonData = spSkeletonJson_readSkeletonDataFile(json, filename);
// ASSERT(skeletonData != 0);
//
// spSkeletonJson_dispose(json);
// return skeletonData;
//}
//
//static void LoadSpineboyExample(spAtlas* &atlas, spSkeletonData* &skeletonData, spAnimationStateData* &stateData, spSkeleton* &skeleton, spAnimationState* &state)
//{
// ///////////////////////////////////////////////////////////////////////////
// // Global Animation Information
// atlas = spAtlas_createFromFile(SPINEBOY_ATLAS, 0);
// ASSERT(atlas != 0);
//
// skeletonData = readSkeletonJsonData(SPINEBOY_JSON, atlas);
// ASSERT(skeletonData != 0);
//
// stateData = spAnimationStateData_create(skeletonData);
// ASSERT(stateData != 0);
// stateData->defaultMix = 0.4f; // force mixing
//
// ///////////////////////////////////////////////////////////////////////////
// // Animation Instance
// skeleton = spSkeleton_create(skeletonData);
// ASSERT(skeleton != 0);
//
// state = spAnimationState_create(stateData);
// ASSERT(state != 0);
//}
//
//static void DisposeAll(spSkeleton* skeleton, spAnimationState* state, spAnimationStateData* stateData, spSkeletonData* skeletonData, spAtlas* atlas)
//{
// ///////////////////////////////////////////////////////////////////////////
// // Dispose Instance
// spSkeleton_dispose(skeleton);
// spAnimationState_dispose(state);
//
// ///////////////////////////////////////////////////////////////////////////
// // Dispose Global
// spAnimationStateData_dispose(stateData);
// spSkeletonData_dispose(skeletonData);
// spAtlas_dispose(atlas);
//}
//
//
////////////////////////////////////////////////////////////////////////////
//// Reproduce Memory leak as described in Issue #776
//// https://github.com/EsotericSoftware/spine-runtimes/issues/776
//void MemoryTestFixture::reproduceIssue_776()
//{
// spAtlas* atlas = 0;
// spSkeletonData* skeletonData = 0;
// spAnimationStateData* stateData = 0;
// spSkeleton* skeleton = 0;
// spAnimationState* state = 0;
//
// //////////////////////////////////////////////////////////////////////////
// // Initialize Animations
// LoadSpineboyExample(atlas, skeletonData, stateData, skeleton, state);
//
// ///////////////////////////////////////////////////////////////////////////
// // Run animation
// spSkeleton_setToSetupPose(skeleton);
// InterruptMonitor eventMonitor(state);
// //eventMonitor.SetDebugLogging(true);
//
// // Interrupt the animation on this specific sequence of spEventType(s)
// eventMonitor
// .AddInterruptEvent(SP_ANIMATION_INTERRUPT, "jump")
// .AddInterruptEvent(SP_ANIMATION_START);
//
// spAnimationState_setAnimationByName(state, 0, "walk", true);
// spAnimationState_addAnimationByName(state, 0, "jump", false, 0.0f);
// spAnimationState_addAnimationByName(state, 0, "run", true, 0.0f);
// spAnimationState_addAnimationByName(state, 0, "jump", false, 3.0f);
// spAnimationState_addAnimationByName(state, 0, "walk", true, 0.0f);
// spAnimationState_addAnimationByName(state, 0, "idle", false, 1.0f);
//
// for (int i = 0; i < MAX_RUN_TIME && eventMonitor.isAnimationPlaying(); ++i) {
// const float timeSlice = 1.0f / 60.0f;
// spSkeleton_update(skeleton, timeSlice);
// spAnimationState_update(state, timeSlice);
// spAnimationState_apply(state, skeleton);
// }
//
// //////////////////////////////////////////////////////////////////////////
// // Cleanup Animations
// DisposeAll(skeleton, state, stateData, skeletonData, atlas);
//}
//
//void MemoryTestFixture::reproduceIssue_777()
//{
// spAtlas* atlas = 0;
// spSkeletonData* skeletonData = 0;
// spAnimationStateData* stateData = 0;
// spSkeleton* skeleton = 0;
// spAnimationState* state = 0;
//
// //////////////////////////////////////////////////////////////////////////
// // Initialize Animations
// LoadSpineboyExample(atlas, skeletonData, stateData, skeleton, state);
//
// ///////////////////////////////////////////////////////////////////////////
// // Run animation
// spSkeleton_setToSetupPose(skeleton);
// SpineEventMonitor eventMonitor(state);
// //eventMonitor.SetDebugLogging(true);
//
// // Set Animation and Play for 5 frames
// spAnimationState_setAnimationByName(state, 0, "walk", true);
// for (int i = 0; i < 5; ++i) {
// const float timeSlice = 1.0f / 60.0f;
// spSkeleton_update(skeleton, timeSlice);
// spAnimationState_update(state, timeSlice);
// spAnimationState_apply(state, skeleton);
// }
//
// // Change animation twice in a row
// spAnimationState_setAnimationByName(state, 0, "walk", false);
// spAnimationState_setAnimationByName(state, 0, "run", false);
//
// // run normal update
// for (int i = 0; i < 5; ++i) {
// const float timeSlice = 1.0f / 60.0f;
// spSkeleton_update(skeleton, timeSlice);
// spAnimationState_update(state, timeSlice);
// spAnimationState_apply(state, skeleton);
// }
//
// // Now we'd lose mixingFrom (the first "walk" entry we set above) and should leak
// spAnimationState_setAnimationByName(state, 0, "run", false);
//
// //////////////////////////////////////////////////////////////////////////
// // Cleanup Animations
// DisposeAll(skeleton, state, stateData, skeletonData, atlas);
//}
//
//spSkeleton* skeleton = 0;
//static void spineAnimStateHandler(spAnimationState* state, int type, spTrackEntry* entry, spEvent* event)
//{
// if (type == SP_ANIMATION_COMPLETE)
// {
// spAnimationState_setAnimationByName(state, 0, "walk", false);
// spAnimationState_update(state, 0);
// spAnimationState_apply(state, skeleton);
// }
//}
//
//void MemoryTestFixture::reproduceIssue_Loop()
//{
// spAtlas* atlas = 0;
// spSkeletonData* skeletonData = 0;
// spAnimationStateData* stateData = 0;
// spAnimationState* state = 0;
//
// //////////////////////////////////////////////////////////////////////////
// // Initialize Animations
// LoadSpineboyExample(atlas, skeletonData, stateData, skeleton, state);
//
// ///////////////////////////////////////////////////////////////////////////
//
// if (state)
// state->listener = (spAnimationStateListener)&spineAnimStateHandler;
//
// spAnimationState_setAnimationByName(state, 0, "walk", false);
//
// // run normal update
// for (int i = 0; i < 50; ++i) {
// const float timeSlice = 1.0f / 60.0f;
// spSkeleton_update(skeleton, timeSlice);
// spAnimationState_update(state, timeSlice);
// spAnimationState_apply(state, skeleton);
// }
//
// DisposeAll(skeleton, state, stateData, skeletonData, atlas);
//}
//
//void MemoryTestFixture::triangulator() {
// spTriangulator* triangulator = spTriangulator_create();
// spFloatArray* polygon = spFloatArray_create(16);
// spFloatArray_add(polygon, 0);
// spFloatArray_add(polygon, 0);
// spFloatArray_add(polygon, 100);
// spFloatArray_add(polygon, 0);
// spFloatArray_add(polygon, 100);
// spFloatArray_add(polygon, 100);
// spFloatArray_add(polygon, 0);
// spFloatArray_add(polygon, 100);
//
// spShortArray* triangles = spTriangulator_triangulate(triangulator, polygon);
// ASSERT(triangles->size == 6);
// ASSERT(triangles->items[0] == 3);
// ASSERT(triangles->items[1] == 0);
// ASSERT(triangles->items[2] == 1);
// ASSERT(triangles->items[3] == 3);
// ASSERT(triangles->items[4] == 1);
// ASSERT(triangles->items[5] == 2);
//
// spArrayFloatArray* polys = spTriangulator_decompose(triangulator, polygon, triangles);
// ASSERT(polys->size == 1);
// ASSERT(polys->items[0]->size == 8);
// ASSERT(polys->items[0]->items[0] == 0);
// ASSERT(polys->items[0]->items[1] == 100);
// ASSERT(polys->items[0]->items[2] == 0);
// ASSERT(polys->items[0]->items[3] == 0);
// ASSERT(polys->items[0]->items[4] == 100);
// ASSERT(polys->items[0]->items[5] == 0);
// ASSERT(polys->items[0]->items[6] == 100);
// ASSERT(polys->items[0]->items[7] == 100);
//
// spFloatArray_dispose(polygon);
// spTriangulator_dispose(triangulator);
//}
//
//void MemoryTestFixture::skeletonClipper() {
// spSkeletonClipping* clipping = spSkeletonClipping_create();
//
// spBoneData* boneData = spBoneData_create(0, "bone", 0);
// spBone* bone = spBone_create(boneData, 0, 0);
// CONST_CAST(float, bone->a) = 1;
// CONST_CAST(float, bone->b) = 0;
// CONST_CAST(float, bone->c) = 0;
// CONST_CAST(float, bone->d) = 1;
// CONST_CAST(float, bone->worldX) = 0;
// CONST_CAST(float, bone->worldY) = 0;
// spSlotData* slotData = spSlotData_create(0, "slot", 0);
// spSlot* slot = spSlot_create(slotData, bone);
// spClippingAttachment* clip = spClippingAttachment_create("clipping");
// clip->endSlot = slotData;
// clip->super.worldVerticesLength = 4 * 2;
// clip->super.verticesCount = 4;
// clip->super.vertices = MALLOC(float, 4 * 8);
// clip->super.vertices[0] = 0;
// clip->super.vertices[1] = 50;
// clip->super.vertices[2] = 100;
// clip->super.vertices[3] = 50;
// clip->super.vertices[4] = 100;
// clip->super.vertices[5] = 70;
// clip->super.vertices[6] = 0;
// clip->super.vertices[7] = 70;
//
// spSkeletonClipping_clipStart(clipping, slot, clip);
//
// spFloatArray* vertices = spFloatArray_create(16);
// spFloatArray_add(vertices, 0);
// spFloatArray_add(vertices, 0);
// spFloatArray_add(vertices, 100);
// spFloatArray_add(vertices, 0);
// spFloatArray_add(vertices, 50);
// spFloatArray_add(vertices, 150);
// spFloatArray* uvs = spFloatArray_create(16);
// spFloatArray_add(uvs, 0);
// spFloatArray_add(uvs, 0);
// spFloatArray_add(uvs, 1);
// spFloatArray_add(uvs, 0);
// spFloatArray_add(uvs, 0.5f);
// spFloatArray_add(uvs, 1);
// spUnsignedShortArray* indices = spUnsignedShortArray_create(16);
// spUnsignedShortArray_add(indices, 0);
// spUnsignedShortArray_add(indices, 1);
// spUnsignedShortArray_add(indices, 2);
//
// spSkeletonClipping_clipTriangles(clipping, vertices->items, vertices->size, indices->items, indices->size, uvs->items, 2);
//
// float expectedVertices[8] = { 83.333328, 50.000000, 76.666664, 70.000000, 23.333334, 70.000000, 16.666672, 50.000000 };
// ASSERT(clipping->clippedVertices->size == 8);
// for (int i = 0; i < clipping->clippedVertices->size; i++) {
// ASSERT(ABS(clipping->clippedVertices->items[i] - expectedVertices[i]) < 0.001);
// }
//
// float expectedUVs[8] = { 0.833333f, 0.333333, 0.766667, 0.466667, 0.233333, 0.466667, 0.166667, 0.333333 };
// ASSERT(clipping->clippedUVs->size == 8);
// for (int i = 0; i < clipping->clippedUVs->size; i++) {
// ASSERT(ABS(clipping->clippedUVs->items[i] - expectedUVs[i]) < 0.001);
// }
//
// short expectedIndices[6] = { 0, 1, 2, 0, 2, 3 };
// ASSERT(clipping->clippedTriangles->size == 6);
// for (int i = 0; i < clipping->clippedTriangles->size; i++) {
// ASSERT(clipping->clippedTriangles->items[i] == expectedIndices[i]);
// }
//
// spFloatArray_dispose(vertices);
// spFloatArray_dispose(uvs);
// spUnsignedShortArray_dispose(indices);
//
// spSlotData_dispose(slotData);
// spSlot_dispose(slot);
// spBoneData_dispose(boneData);
// spBone_dispose(bone);
// _spClippingAttachment_dispose(SUPER(SUPER(clip)));
// spSkeletonClipping_dispose(clipping);
//}
//
//
#include <spine/Extension.h>
#include "MemoryTestFixture.h"
#include "SpineEventMonitor.h"
#include "KMemory.h" // last include
#define SPINEBOY_JSON "testdata/spineboy/spineboy-ess.json"
#define SPINEBOY_ATLAS "testdata/spineboy/spineboy.atlas"
#define MAX_RUN_TIME 6000 // equal to about 100 seconds of execution
MemoryTestFixture::~MemoryTestFixture()
{
finalize();
}
void MemoryTestFixture::initialize()
{
// on a Per- Fixture Basis, before Test execution
}
void MemoryTestFixture::finalize()
{
// on a Per- Fixture Basis, after all tests pass/fail
}
void MemoryTestFixture::setUp()
{
// Setup on Per-Test Basis
}
void MemoryTestFixture::tearDown()
{
// Tear Down on Per-Test Basis
}
//////////////////////////////////////////////////////////////////////////
// Helper methods
static spSkeletonData* readSkeletonJsonData(const char* filename, spAtlas* atlas) {
spSkeletonJson* json = spSkeletonJson_create(atlas);
ASSERT(json != 0);
spSkeletonData* skeletonData = spSkeletonJson_readSkeletonDataFile(json, filename);
ASSERT(skeletonData != 0);
spSkeletonJson_dispose(json);
return skeletonData;
}
static void LoadSpineboyExample(spAtlas* &atlas, spSkeletonData* &skeletonData, spAnimationStateData* &stateData, spSkeleton* &skeleton, spAnimationState* &state)
{
///////////////////////////////////////////////////////////////////////////
// Global Animation Information
atlas = spAtlas_createFromFile(SPINEBOY_ATLAS, 0);
ASSERT(atlas != 0);
skeletonData = readSkeletonJsonData(SPINEBOY_JSON, atlas);
ASSERT(skeletonData != 0);
stateData = spAnimationStateData_create(skeletonData);
ASSERT(stateData != 0);
stateData->defaultMix = 0.4f; // force mixing
///////////////////////////////////////////////////////////////////////////
// Animation Instance
skeleton = spSkeleton_create(skeletonData);
ASSERT(skeleton != 0);
state = spAnimationState_create(stateData);
ASSERT(state != 0);
}
static void DisposeAll(spSkeleton* skeleton, spAnimationState* state, spAnimationStateData* stateData, spSkeletonData* skeletonData, spAtlas* atlas)
{
///////////////////////////////////////////////////////////////////////////
// Dispose Instance
spSkeleton_dispose(skeleton);
spAnimationState_dispose(state);
///////////////////////////////////////////////////////////////////////////
// Dispose Global
spAnimationStateData_dispose(stateData);
spSkeletonData_dispose(skeletonData);
spAtlas_dispose(atlas);
}
//////////////////////////////////////////////////////////////////////////
// Reproduce Memory leak as described in Issue #776
// https://github.com/EsotericSoftware/spine-runtimes/issues/776
void MemoryTestFixture::reproduceIssue_776()
{
spAtlas* atlas = 0;
spSkeletonData* skeletonData = 0;
spAnimationStateData* stateData = 0;
spSkeleton* skeleton = 0;
spAnimationState* state = 0;
//////////////////////////////////////////////////////////////////////////
// Initialize Animations
LoadSpineboyExample(atlas, skeletonData, stateData, skeleton, state);
///////////////////////////////////////////////////////////////////////////
// Run animation
spSkeleton_setToSetupPose(skeleton);
InterruptMonitor eventMonitor(state);
//eventMonitor.SetDebugLogging(true);
// Interrupt the animation on this specific sequence of spEventType(s)
eventMonitor
.AddInterruptEvent(SP_ANIMATION_INTERRUPT, "jump")
.AddInterruptEvent(SP_ANIMATION_START);
spAnimationState_setAnimationByName(state, 0, "walk", true);
spAnimationState_addAnimationByName(state, 0, "jump", false, 0.0f);
spAnimationState_addAnimationByName(state, 0, "run", true, 0.0f);
spAnimationState_addAnimationByName(state, 0, "jump", false, 3.0f);
spAnimationState_addAnimationByName(state, 0, "walk", true, 0.0f);
spAnimationState_addAnimationByName(state, 0, "idle", false, 1.0f);
for (int i = 0; i < MAX_RUN_TIME && eventMonitor.isAnimationPlaying(); ++i) {
const float timeSlice = 1.0f / 60.0f;
spSkeleton_update(skeleton, timeSlice);
spAnimationState_update(state, timeSlice);
spAnimationState_apply(state, skeleton);
}
//////////////////////////////////////////////////////////////////////////
// Cleanup Animations
DisposeAll(skeleton, state, stateData, skeletonData, atlas);
}
void MemoryTestFixture::reproduceIssue_777()
{
spAtlas* atlas = 0;
spSkeletonData* skeletonData = 0;
spAnimationStateData* stateData = 0;
spSkeleton* skeleton = 0;
spAnimationState* state = 0;
//////////////////////////////////////////////////////////////////////////
// Initialize Animations
LoadSpineboyExample(atlas, skeletonData, stateData, skeleton, state);
///////////////////////////////////////////////////////////////////////////
// Run animation
spSkeleton_setToSetupPose(skeleton);
SpineEventMonitor eventMonitor(state);
//eventMonitor.SetDebugLogging(true);
// Set Animation and Play for 5 frames
spAnimationState_setAnimationByName(state, 0, "walk", true);
for (int i = 0; i < 5; ++i) {
const float timeSlice = 1.0f / 60.0f;
spSkeleton_update(skeleton, timeSlice);
spAnimationState_update(state, timeSlice);
spAnimationState_apply(state, skeleton);
}
// Change animation twice in a row
spAnimationState_setAnimationByName(state, 0, "walk", false);
spAnimationState_setAnimationByName(state, 0, "run", false);
// run normal update
for (int i = 0; i < 5; ++i) {
const float timeSlice = 1.0f / 60.0f;
spSkeleton_update(skeleton, timeSlice);
spAnimationState_update(state, timeSlice);
spAnimationState_apply(state, skeleton);
}
// Now we'd lose mixingFrom (the first "walk" entry we set above) and should leak
spAnimationState_setAnimationByName(state, 0, "run", false);
//////////////////////////////////////////////////////////////////////////
// Cleanup Animations
DisposeAll(skeleton, state, stateData, skeletonData, atlas);
}
spSkeleton* skeleton = 0;
static void spineAnimStateHandler(spAnimationState* state, int type, spTrackEntry* entry, spEvent* event)
{
if (type == SP_ANIMATION_COMPLETE)
{
spAnimationState_setAnimationByName(state, 0, "walk", false);
spAnimationState_update(state, 0);
spAnimationState_apply(state, skeleton);
}
}
void MemoryTestFixture::reproduceIssue_Loop()
{
spAtlas* atlas = 0;
spSkeletonData* skeletonData = 0;
spAnimationStateData* stateData = 0;
spAnimationState* state = 0;
//////////////////////////////////////////////////////////////////////////
// Initialize Animations
LoadSpineboyExample(atlas, skeletonData, stateData, skeleton, state);
///////////////////////////////////////////////////////////////////////////
if (state)
state->listener = (spAnimationStateListener)&spineAnimStateHandler;
spAnimationState_setAnimationByName(state, 0, "walk", false);
// run normal update
for (int i = 0; i < 50; ++i) {
const float timeSlice = 1.0f / 60.0f;
spSkeleton_update(skeleton, timeSlice);
spAnimationState_update(state, timeSlice);
spAnimationState_apply(state, skeleton);
}
DisposeAll(skeleton, state, stateData, skeletonData, atlas);
}
void MemoryTestFixture::triangulator() {
spTriangulator* triangulator = spTriangulator_create();
spFloatArray* polygon = spFloatArray_create(16);
spFloatArray_add(polygon, 0);
spFloatArray_add(polygon, 0);
spFloatArray_add(polygon, 100);
spFloatArray_add(polygon, 0);
spFloatArray_add(polygon, 100);
spFloatArray_add(polygon, 100);
spFloatArray_add(polygon, 0);
spFloatArray_add(polygon, 100);
spShortArray* triangles = spTriangulator_triangulate(triangulator, polygon);
ASSERT(triangles->size == 6);
ASSERT(triangles->items[0] == 3);
ASSERT(triangles->items[1] == 0);
ASSERT(triangles->items[2] == 1);
ASSERT(triangles->items[3] == 3);
ASSERT(triangles->items[4] == 1);
ASSERT(triangles->items[5] == 2);
spArrayFloatArray* polys = spTriangulator_decompose(triangulator, polygon, triangles);
ASSERT(polys->size == 1);
ASSERT(polys->items[0]->size == 8);
ASSERT(polys->items[0]->items[0] == 0);
ASSERT(polys->items[0]->items[1] == 100);
ASSERT(polys->items[0]->items[2] == 0);
ASSERT(polys->items[0]->items[3] == 0);
ASSERT(polys->items[0]->items[4] == 100);
ASSERT(polys->items[0]->items[5] == 0);
ASSERT(polys->items[0]->items[6] == 100);
ASSERT(polys->items[0]->items[7] == 100);
spFloatArray_dispose(polygon);
spTriangulator_dispose(triangulator);
}
void MemoryTestFixture::skeletonClipper() {
spSkeletonClipping* clipping = spSkeletonClipping_create();
spBoneData* boneData = spBoneData_create(0, "bone", 0);
spBone* bone = spBone_create(boneData, 0, 0);
CONST_CAST(float, bone->a) = 1;
CONST_CAST(float, bone->b) = 0;
CONST_CAST(float, bone->c) = 0;
CONST_CAST(float, bone->d) = 1;
CONST_CAST(float, bone->worldX) = 0;
CONST_CAST(float, bone->worldY) = 0;
spSlotData* slotData = spSlotData_create(0, "slot", 0);
spSlot* slot = spSlot_create(slotData, bone);
spClippingAttachment* clip = spClippingAttachment_create("clipping");
clip->endSlot = slotData;
clip->super.worldVerticesLength = 4 * 2;
clip->super.verticesCount = 4;
clip->super.vertices = MALLOC(float, 4 * 8);
clip->super.vertices[0] = 0;
clip->super.vertices[1] = 50;
clip->super.vertices[2] = 100;
clip->super.vertices[3] = 50;
clip->super.vertices[4] = 100;
clip->super.vertices[5] = 70;
clip->super.vertices[6] = 0;
clip->super.vertices[7] = 70;
spSkeletonClipping_clipStart(clipping, slot, clip);
spFloatArray* vertices = spFloatArray_create(16);
spFloatArray_add(vertices, 0);
spFloatArray_add(vertices, 0);
spFloatArray_add(vertices, 100);
spFloatArray_add(vertices, 0);
spFloatArray_add(vertices, 50);
spFloatArray_add(vertices, 150);
spFloatArray* uvs = spFloatArray_create(16);
spFloatArray_add(uvs, 0);
spFloatArray_add(uvs, 0);
spFloatArray_add(uvs, 1);
spFloatArray_add(uvs, 0);
spFloatArray_add(uvs, 0.5f);
spFloatArray_add(uvs, 1);
spUnsignedShortArray* indices = spUnsignedShortArray_create(16);
spUnsignedShortArray_add(indices, 0);
spUnsignedShortArray_add(indices, 1);
spUnsignedShortArray_add(indices, 2);
spSkeletonClipping_clipTriangles(clipping, vertices->items, vertices->size, indices->items, indices->size, uvs->items, 2);
float expectedVertices[8] = { 83.333328, 50.000000, 76.666664, 70.000000, 23.333334, 70.000000, 16.666672, 50.000000 };
ASSERT(clipping->clippedVertices->size == 8);
for (int i = 0; i < clipping->clippedVertices->size; i++) {
ASSERT(ABS(clipping->clippedVertices->items[i] - expectedVertices[i]) < 0.001);
}
float expectedUVs[8] = { 0.833333f, 0.333333, 0.766667, 0.466667, 0.233333, 0.466667, 0.166667, 0.333333 };
ASSERT(clipping->clippedUVs->size == 8);
for (int i = 0; i < clipping->clippedUVs->size; i++) {
ASSERT(ABS(clipping->clippedUVs->items[i] - expectedUVs[i]) < 0.001);
}
short expectedIndices[6] = { 0, 1, 2, 0, 2, 3 };
ASSERT(clipping->clippedTriangles->size == 6);
for (int i = 0; i < clipping->clippedTriangles->size; i++) {
ASSERT(clipping->clippedTriangles->items[i] == expectedIndices[i]);
}
spFloatArray_dispose(vertices);
spFloatArray_dispose(uvs);
spUnsignedShortArray_dispose(indices);
spSlotData_dispose(slotData);
spSlot_dispose(slot);
spBoneData_dispose(boneData);
spBone_dispose(bone);
_spClippingAttachment_dispose(SUPER(SUPER(clip)));
spSkeletonClipping_dispose(clipping);
}

View File

@ -116,7 +116,7 @@ InterruptMonitor& InterruptMonitor::AddInterruptEvent(int theEventType, TrackEnt
InterruptMonitor& InterruptMonitor::AddInterruptEventTrigger(const std::string & theEventTriggerName)
{
InterruptEvent ev;
// ev.mEventType = SP_ANIMATION_EVENT;
ev.mEventType = EventType_Event;
ev.mEventName = theEventTriggerName;
mEventStack.push_back(ev);
return *this;
@ -143,7 +143,7 @@ void InterruptMonitor::OnSpineAnimationStateEvent(AnimationState * state, EventT
inline bool InterruptMonitor::InterruptEvent::matches(AnimationState * state, EventType type, TrackEntry * trackEntry, Event * event)
{
// Must match spEventType {SP_ANIMATION_START, SP_ANIMATION_INTERRUPT, SP_ANIMATION_END, SP_ANIMATION_COMPLETE, SP_ANIMATION_DISPOSE, SP_ANIMATION_EVENT }
// Must match EventType {EventType_Start, EventType_Interrupt, EventType_End, EventType_Complete, EventType_Dispose, EventType_Event }
if (mEventType == type)
{
// Looking for specific TrackEntry by pointer

View File

@ -162,12 +162,18 @@ namespace Spine
void setAShearY(float inValue);
float getA();
void setA(float inValue);
float getB();
void setB(float inValue);
float getC();
void setC(float inValue);
float getD();
void setD(float inValue);
float getWorldX();
void setWorldX(float inValue);
float getWorldY();
void setWorldY(float inValue);
float getWorldRotationX();
float getWorldRotationY();

View File

@ -54,6 +54,10 @@ namespace Spine
bool isClipping();
Vector<float>& getClippedVertices();
Vector<int>& getClippedTriangles();
Vector<float>& getClippedUVs();
private:
Triangulator _triangulator;
Vector<float> _clippingPolygon;

View File

@ -66,6 +66,7 @@ namespace Spine
{
clear();
deallocate(_buffer);
_buffer = NULL;
_size = inVector._size;
_capacity = inVector._capacity;
@ -194,6 +195,8 @@ namespace Spine
void setSize(size_t inValue)
{
assert(inValue <= _capacity);
_size = inValue;
}

View File

@ -230,7 +230,7 @@ namespace Spine
_timelineDipMix.clear();
_timelinesRotation.clear();
_onAnimationEventFunc = NULL;
_onAnimationEventFunc = dummyOnAnimationEventFunc;
}
EventQueueEntry::EventQueueEntry(EventType eventType, TrackEntry* trackEntry, Event* event) :

View File

@ -521,31 +521,61 @@ namespace Spine
return _a;
}
void Bone::setA(float inValue)
{
_a = inValue;
}
float Bone::getB()
{
return _b;
}
void Bone::setB(float inValue)
{
_b = inValue;
}
float Bone::getC()
{
return _c;
}
void Bone::setC(float inValue)
{
_c = inValue;
}
float Bone::getD()
{
return _d;
}
void Bone::setD(float inValue)
{
_d = inValue;
}
float Bone::getWorldX()
{
return _worldX;
}
void Bone::setWorldX(float inValue)
{
_worldX = inValue;
}
float Bone::getWorldY()
{
return _worldY;
}
void Bone::setWorldY(float inValue)
{
_worldY = inValue;
}
float Bone::getWorldRotationX()
{
return MathUtil::atan2(_c, _a) * RadDeg;

View File

@ -54,6 +54,7 @@ namespace Spine
int n = clip->getWorldVerticesLength();
_clippingPolygon.reserve(n);
_clippingPolygon.setSize(n);
clip->computeWorldVertices(slot, 0, n, _clippingPolygon, 0, 2);
makeClockwise(_clippingPolygon);
Vector< Vector<float>* > clippingPolygons = _triangulator.decompose(_clippingPolygon, _triangulator.triangulate(_clippingPolygon));
@ -96,7 +97,8 @@ namespace Spine
void SkeletonClipping::clipTriangles(Vector<float>& vertices, int verticesLength, Vector<int>& triangles, int trianglesLength, Vector<float>& uvs)
{
Vector<float>& clipOutput = _clipOutput, clippedVertices = _clippedVertices;
Vector<float>& clipOutput = _clipOutput;
Vector<float>& clippedVertices = _clippedVertices;
Vector<int>& clippedTriangles = _clippedTriangles;
Vector< Vector<float>* >& polygons = _clippingPolygons;
int polygonsCount = static_cast<int>(_clippingPolygons.size());
@ -135,7 +137,9 @@ namespace Spine
int clipOutputCount = clipOutputLength >> 1;
clippedVertices.reserve(s + clipOutputCount * 2);
clippedVertices.setSize(s + clipOutputCount * 2);
_clippedUVs.reserve(s + clipOutputCount * 2);
_clippedUVs.setSize(s + clipOutputCount * 2);
for (int ii = 0; ii < clipOutputLength; ii += 2)
{
float x = clipOutput[ii], y = clipOutput[ii + 1];
@ -152,6 +156,7 @@ namespace Spine
s = static_cast<int>(clippedTriangles.size());
clippedTriangles.reserve(s + 3 * (clipOutputCount - 2));
clippedTriangles.setSize(s + 3 * (clipOutputCount - 2));
clipOutputCount--;
for (int ii = 1; ii < clipOutputCount; ii++)
{
@ -165,7 +170,9 @@ namespace Spine
else
{
clippedVertices.reserve(s + 3 * 2);
clippedVertices.setSize(s + 3 * 2);
_clippedUVs.reserve(s + 3 * 2);
_clippedUVs.setSize(s + 3 * 2);
clippedVertices[s] = x1;
clippedVertices[s + 1] = y1;
clippedVertices[s + 2] = x2;
@ -182,6 +189,7 @@ namespace Spine
s = static_cast<int>(clippedTriangles.size());
clippedTriangles.reserve(s + 3);
clippedTriangles.setSize(s + 3);
clippedTriangles[s] = index;
clippedTriangles[s + 1] = index + 1;
clippedTriangles[s + 2] = index + 2;
@ -197,9 +205,24 @@ namespace Spine
return _clipAttachment != NULL;
}
Vector<float>& SkeletonClipping::getClippedVertices()
{
return _clippedVertices;
}
Vector<int>& SkeletonClipping::getClippedTriangles()
{
return _clippedTriangles;
}
Vector<float>& SkeletonClipping::getClippedUVs()
{
return _clippedUVs;
}
bool SkeletonClipping::clip(float x1, float y1, float x2, float y2, float x3, float y3, Vector<float>& clippingArea, Vector<float>& output)
{
Vector<float> originalOutput = output;
Vector<float>& originalOutput = output;
bool clipped = false;
// Avoid copy at the end.
@ -225,7 +248,7 @@ namespace Spine
input.push_back(y1);
output.clear();
Vector<float> clippingVertices = clippingArea;
Vector<float>& clippingVertices = clippingArea;
int clippingVerticesLast = static_cast<int>(clippingArea.size()) - 4;
for (int i = 0; ; i += 2)
{
@ -233,7 +256,7 @@ namespace Spine
float edgeX2 = clippingVertices[i + 2], edgeY2 = clippingVertices[i + 3];
float deltaX = edgeX - edgeX2, deltaY = edgeY - edgeY2;
Vector<float> inputVertices = input;
Vector<float>& inputVertices = input;
int inputVerticesLength = static_cast<int>(input.size()) - 2, outputStart = static_cast<int>(output.size());
for (int ii = 0; ii < inputVerticesLength; ii += 2)
{
@ -299,6 +322,7 @@ namespace Spine
else
{
originalOutput.reserve(originalOutput.size() - 2);
originalOutput.setSize(originalOutput.size() - 2);
}
return clipped;