Merge branch '3.7-beta' into 3.7-beta-cpp

This commit is contained in:
badlogic 2018-10-26 17:12:08 +02:00
commit bb1bdd7046
26 changed files with 549 additions and 291 deletions

1
.gitignore vendored
View File

@ -41,6 +41,7 @@ spine-cocos2dx/example/cocos2d
spine-cocos2dx/example/proj.win32/spine-cocos2d-x.VC.opendb spine-cocos2dx/example/proj.win32/spine-cocos2d-x.VC.opendb
spine-cocos2dx/example/proj.win32/spine-cocos2d-x.VC.db spine-cocos2dx/example/proj.win32/spine-cocos2d-x.VC.db
xcuserdata/ xcuserdata/
xcshareddata/
spine-cocos2d-objc/cocos2d/* spine-cocos2d-objc/cocos2d/*
spine-cocos2d-objc/spine-cocos2d-iphone-objc.xcodeproj/project.xcworkspace/xcshareddata/ spine-cocos2d-objc/spine-cocos2d-iphone-objc.xcodeproj/project.xcworkspace/xcshareddata/

View File

@ -74,7 +74,7 @@ bool AppDelegate::applicationDidFinishLaunching () {
glview->setDesignResolutionSize(designResolutionSize.width, designResolutionSize.height, ResolutionPolicy::NO_BORDER); glview->setDesignResolutionSize(designResolutionSize.width, designResolutionSize.height, ResolutionPolicy::NO_BORDER);
#endif #endif
Size frameSize = glview->getFrameSize(); cocos2d::Size frameSize = glview->getFrameSize();
vector<string> searchPath; vector<string> searchPath;

View File

@ -70,7 +70,7 @@ bool BatchingExample::init () {
int xMin = _contentSize.width * 0.10f, xMax = _contentSize.width * 0.90f; int xMin = _contentSize.width * 0.10f, xMax = _contentSize.width * 0.90f;
int yMin = 0, yMax = _contentSize.height * 0.7f; int yMin = 0, yMax = _contentSize.height * 0.7f;
for (int i = 0, j = 0; i < NUM_SKELETONS; i++) { for (int i = 0; i < NUM_SKELETONS; i++) {
// Each skeleton node shares the same atlas, skeleton data, and mix times. // Each skeleton node shares the same atlas, skeleton data, and mix times.
SkeletonAnimation* skeletonNode = SkeletonAnimation::createWithData(_skeletonData, false); SkeletonAnimation* skeletonNode = SkeletonAnimation::createWithData(_skeletonData, false);
skeletonNode->setAnimationStateData(_stateData); skeletonNode->setAnimationStateData(_stateData);

View File

@ -36,127 +36,127 @@
namespace spine { namespace spine {
class AttachmentVertices; class AttachmentVertices;
/* Draws a skeleton. */ /* Draws a skeleton. */
class SkeletonRenderer: public cocos2d::Node, public cocos2d::BlendProtocol { class SkeletonRenderer: public cocos2d::Node, public cocos2d::BlendProtocol {
public: public:
CREATE_FUNC(SkeletonRenderer); CREATE_FUNC(SkeletonRenderer);
static SkeletonRenderer* createWithSkeleton(Skeleton* skeleton, bool ownsSkeleton = false, bool ownsSkeletonData = false); static SkeletonRenderer* createWithSkeleton(Skeleton* skeleton, bool ownsSkeleton = false, bool ownsSkeletonData = false);
static SkeletonRenderer* createWithData (SkeletonData* skeletonData, bool ownsSkeletonData = false); static SkeletonRenderer* createWithData (SkeletonData* skeletonData, bool ownsSkeletonData = false);
static SkeletonRenderer* createWithFile (const std::string& skeletonDataFile, Atlas* atlas, float scale = 1); static SkeletonRenderer* createWithFile (const std::string& skeletonDataFile, Atlas* atlas, float scale = 1);
static SkeletonRenderer* createWithFile (const std::string& skeletonDataFile, const std::string& atlasFile, float scale = 1); static SkeletonRenderer* createWithFile (const std::string& skeletonDataFile, const std::string& atlasFile, float scale = 1);
virtual void update (float deltaTime) override; virtual void update (float deltaTime) override;
virtual void draw (cocos2d::Renderer* renderer, const cocos2d::Mat4& transform, uint32_t transformFlags) override; virtual void draw (cocos2d::Renderer* renderer, const cocos2d::Mat4& transform, uint32_t transformFlags) override;
virtual void drawDebug (cocos2d::Renderer* renderer, const cocos2d::Mat4& transform, uint32_t transformFlags); virtual void drawDebug (cocos2d::Renderer* renderer, const cocos2d::Mat4& transform, uint32_t transformFlags);
virtual cocos2d::Rect getBoundingBox () const override; virtual cocos2d::Rect getBoundingBox () const override;
virtual void onEnter () override; virtual void onEnter () override;
virtual void onExit () override; virtual void onExit () override;
Skeleton* getSkeleton(); Skeleton* getSkeleton();
void setTimeScale(float scale); void setTimeScale(float scale);
float getTimeScale() const; float getTimeScale() const;
/* */ /* */
void setDebugSlotsEnabled(bool enabled); void setDebugSlotsEnabled(bool enabled);
bool getDebugSlotsEnabled() const; bool getDebugSlotsEnabled() const;
void setDebugBonesEnabled(bool enabled); void setDebugBonesEnabled(bool enabled);
bool getDebugBonesEnabled() const; bool getDebugBonesEnabled() const;
void setDebugMeshesEnabled(bool enabled); void setDebugMeshesEnabled(bool enabled);
bool getDebugMeshesEnabled() const; bool getDebugMeshesEnabled() const;
// --- Convenience methods for common Skeleton_* functions. // --- Convenience methods for common Skeleton_* functions.
void updateWorldTransform (); void updateWorldTransform ();
void setToSetupPose (); void setToSetupPose ();
void setBonesToSetupPose (); void setBonesToSetupPose ();
void setSlotsToSetupPose (); void setSlotsToSetupPose ();
/* Returns 0 if the bone was not found. */ /* Returns 0 if the bone was not found. */
Bone* findBone (const std::string& boneName) const; Bone* findBone (const std::string& boneName) const;
/* Returns 0 if the slot was not found. */ /* Returns 0 if the slot was not found. */
Slot* findSlot (const std::string& slotName) const; Slot* findSlot (const std::string& slotName) const;
/* Sets the skin used to look up attachments not found in the SkeletonData defaultSkin. Attachments from the new skin are /* Sets the skin used to look up attachments not found in the SkeletonData defaultSkin. Attachments from the new skin are
* attached if the corresponding attachment from the old skin was attached. * attached if the corresponding attachment from the old skin was attached.
* @param skin May be empty string ("") for no skin.*/ * @param skin May be empty string ("") for no skin.*/
void setSkin (const std::string& skinName); void setSkin (const std::string& skinName);
/** @param skin May be 0 for no skin.*/ /** @param skin May be 0 for no skin.*/
void setSkin (const char* skinName); void setSkin (const char* skinName);
/* Returns 0 if the slot or attachment was not found. */ /* Returns 0 if the slot or attachment was not found. */
Attachment* getAttachment (const std::string& slotName, const std::string& attachmentName) const; Attachment* getAttachment (const std::string& slotName, const std::string& attachmentName) const;
/* Returns false if the slot or attachment was not found. /* Returns false if the slot or attachment was not found.
* @param attachmentName May be empty string ("") for no attachment. */ * @param attachmentName May be empty string ("") for no attachment. */
bool setAttachment (const std::string& slotName, const std::string& attachmentName); bool setAttachment (const std::string& slotName, const std::string& attachmentName);
/* @param attachmentName May be 0 for no attachment. */ /* @param attachmentName May be 0 for no attachment. */
bool setAttachment (const std::string& slotName, const char* attachmentName); bool setAttachment (const std::string& slotName, const char* attachmentName);
/* Enables/disables two color tinting for this instance. May break batching */ /* Enables/disables two color tinting for this instance. May break batching */
void setTwoColorTint(bool enabled); void setTwoColorTint(bool enabled);
/* Whether two color tinting is enabled */ /* Whether two color tinting is enabled */
bool isTwoColorTint(); bool isTwoColorTint();
/* Sets the vertex effect to be used, set to 0 to disable vertex effects */ /* Sets the vertex effect to be used, set to 0 to disable vertex effects */
void setVertexEffect(VertexEffect* effect); void setVertexEffect(VertexEffect* effect);
/* Sets the range of slots that should be rendered. Use -1, -1 to clear the range */ /* Sets the range of slots that should be rendered. Use -1, -1 to clear the range */
void setSlotsRange(int startSlotIndex, int endSlotIndex); void setSlotsRange(int startSlotIndex, int endSlotIndex);
// --- BlendProtocol // --- BlendProtocol
virtual void setBlendFunc (const cocos2d::BlendFunc& blendFunc)override; virtual void setBlendFunc (const cocos2d::BlendFunc& blendFunc)override;
virtual const cocos2d::BlendFunc& getBlendFunc () const override; virtual const cocos2d::BlendFunc& getBlendFunc () const override;
virtual void setOpacityModifyRGB (bool value) override; virtual void setOpacityModifyRGB (bool value) override;
virtual bool isOpacityModifyRGB () const override; virtual bool isOpacityModifyRGB () const override;
// Frees global memory used for temporay vertex transformations. // Frees global memory used for temporay vertex transformations.
static void destroyScratchBuffers(); static void destroyScratchBuffers();
CC_CONSTRUCTOR_ACCESS: CC_CONSTRUCTOR_ACCESS:
SkeletonRenderer (); SkeletonRenderer ();
SkeletonRenderer(Skeleton* skeleton, bool ownsSkeleton = false, bool ownsSkeletonData = false, bool ownsAtlas = false); SkeletonRenderer(Skeleton* skeleton, bool ownsSkeleton = false, bool ownsSkeletonData = false, bool ownsAtlas = false);
SkeletonRenderer (SkeletonData* skeletonData, bool ownsSkeletonData = false); SkeletonRenderer (SkeletonData* skeletonData, bool ownsSkeletonData = false);
SkeletonRenderer (const std::string& skeletonDataFile, Atlas* atlas, float scale = 1); SkeletonRenderer (const std::string& skeletonDataFile, Atlas* atlas, float scale = 1);
SkeletonRenderer (const std::string& skeletonDataFile, const std::string& atlasFile, float scale = 1); SkeletonRenderer (const std::string& skeletonDataFile, const std::string& atlasFile, float scale = 1);
virtual ~SkeletonRenderer (); virtual ~SkeletonRenderer ();
void initWithSkeleton(Skeleton* skeleton, bool ownsSkeleton = false, bool ownsSkeletonData = false, bool ownsAtlas = false); void initWithSkeleton(Skeleton* skeleton, bool ownsSkeleton = false, bool ownsSkeletonData = false, bool ownsAtlas = false);
void initWithData (SkeletonData* skeletonData, bool ownsSkeletonData = false); void initWithData (SkeletonData* skeletonData, bool ownsSkeletonData = false);
void initWithJsonFile (const std::string& skeletonDataFile, Atlas* atlas, float scale = 1); void initWithJsonFile (const std::string& skeletonDataFile, Atlas* atlas, float scale = 1);
void initWithJsonFile (const std::string& skeletonDataFile, const std::string& atlasFile, float scale = 1); void initWithJsonFile (const std::string& skeletonDataFile, const std::string& atlasFile, float scale = 1);
void initWithBinaryFile (const std::string& skeletonDataFile, Atlas* atlas, float scale = 1); void initWithBinaryFile (const std::string& skeletonDataFile, Atlas* atlas, float scale = 1);
void initWithBinaryFile (const std::string& skeletonDataFile, const std::string& atlasFile, float scale = 1); void initWithBinaryFile (const std::string& skeletonDataFile, const std::string& atlasFile, float scale = 1);
virtual void initialize (); virtual void initialize ();
protected: protected:
void setSkeletonData (SkeletonData* skeletonData, bool ownsSkeletonData); void setSkeletonData (SkeletonData* skeletonData, bool ownsSkeletonData);
void setupGLProgramState(bool twoColorTintEnabled); void setupGLProgramState(bool twoColorTintEnabled);
bool _ownsSkeletonData; bool _ownsSkeletonData;
bool _ownsSkeleton; bool _ownsSkeleton;
bool _ownsAtlas; bool _ownsAtlas;
Atlas* _atlas; Atlas* _atlas;
AttachmentLoader* _attachmentLoader; AttachmentLoader* _attachmentLoader;
cocos2d::CustomCommand _debugCommand; cocos2d::CustomCommand _debugCommand;
cocos2d::BlendFunc _blendFunc; cocos2d::BlendFunc _blendFunc;
bool _premultipliedAlpha; bool _premultipliedAlpha;
Skeleton* _skeleton; Skeleton* _skeleton;
float _timeScale; float _timeScale;
bool _debugSlots; bool _debugSlots;
bool _debugBones; bool _debugBones;
bool _debugMeshes; bool _debugMeshes;
SkeletonClipping* _clipper; SkeletonClipping* _clipper;
VertexEffect* _effect; VertexEffect* _effect;
int _startSlotIndex; int _startSlotIndex;
int _endSlotIndex; int _endSlotIndex;
}; };
} }

View File

@ -57,7 +57,7 @@ void TwoColorTrianglesCommand::init(float globalOrder, GLuint textureID, GLProgr
if(_triangles.indexCount % 3 != 0) { if(_triangles.indexCount % 3 != 0) {
int count = _triangles.indexCount; int count = _triangles.indexCount;
_triangles.indexCount = count / 3 * 3; _triangles.indexCount = count / 3 * 3;
CCLOGERROR("Resize indexCount from %zd to %zd, size must be multiple times of 3", count, _triangles.indexCount); CCLOGERROR("Resize indexCount from %d to %d, size must be multiple times of 3", count, _triangles.indexCount);
} }
_mv = mv; _mv = mv;

View File

@ -329,10 +329,11 @@ namespace Spine {
public float WorldToLocalRotation (float worldRotation) { public float WorldToLocalRotation (float worldRotation) {
float sin = MathUtils.SinDeg(worldRotation), cos = MathUtils.CosDeg(worldRotation); float sin = MathUtils.SinDeg(worldRotation), cos = MathUtils.CosDeg(worldRotation);
return MathUtils.Atan2(a * sin - c * cos, d * cos - b * sin) * MathUtils.RadDeg; return MathUtils.Atan2(a * sin - c * cos, d * cos - b * sin) * MathUtils.RadDeg + rotation - shearX;
} }
public float LocalToWorldRotation (float localRotation) { public float LocalToWorldRotation (float localRotation) {
localRotation -= rotation - shearX;
float sin = MathUtils.SinDeg(localRotation), cos = MathUtils.CosDeg(localRotation); float sin = MathUtils.SinDeg(localRotation), cos = MathUtils.CosDeg(localRotation);
return MathUtils.Atan2(cos * c + sin * d, cos * a + sin * b) * MathUtils.RadDeg; return MathUtils.Atan2(cos * c + sin * d, cos * a + sin * b) * MathUtils.RadDeg;
} }

View File

@ -127,11 +127,11 @@ public class AnimationState {
float nextTime = current.trackLast - next.delay; float nextTime = current.trackLast - next.delay;
if (nextTime >= 0) { if (nextTime >= 0) {
next.delay = 0; next.delay = 0;
next.trackTime = nextTime + delta * next.timeScale; next.trackTime = (nextTime / current.timeScale + delta) * next.timeScale;
current.trackTime += currentDelta; current.trackTime += currentDelta;
setCurrent(i, next, true); setCurrent(i, next, true);
while (next.mixingFrom != null) { while (next.mixingFrom != null) {
next.mixTime += currentDelta; next.mixTime += delta;
next = next.mixingFrom; next = next.mixingFrom;
} }
continue; continue;
@ -182,15 +182,8 @@ public class AnimationState {
return finished; return finished;
} }
// If to has 0 timeScale and is not the first entry, remove the mix and apply it one more time to return to the setup pose.
if (to.timeScale == 0 && to.mixingTo != null) {
to.timeScale = 1;
to.mixTime = 0;
to.mixDuration = 0;
}
from.trackTime += delta * from.timeScale; from.trackTime += delta * from.timeScale;
to.mixTime += delta * to.timeScale; to.mixTime += delta;
return false; return false;
} }
@ -424,10 +417,10 @@ public class AnimationState {
} }
} }
/** Removes all animations from all tracks, leaving skeletons in their previous pose. /** Removes all animations from all tracks, leaving skeletons in their current pose.
* <p> * <p>
* It may be desired to use {@link AnimationState#setEmptyAnimations(float)} to mix the skeletons back to the setup pose, * It may be desired to use {@link AnimationState#setEmptyAnimations(float)} to mix the skeletons back to the setup pose,
* rather than leaving them in their previous pose. */ * rather than leaving them in their current pose. */
public void clearTracks () { public void clearTracks () {
boolean oldDrainDisabled = queue.drainDisabled; boolean oldDrainDisabled = queue.drainDisabled;
queue.drainDisabled = true; queue.drainDisabled = true;
@ -438,10 +431,10 @@ public class AnimationState {
queue.drain(); queue.drain();
} }
/** Removes all animations from the track, leaving skeletons in their previous pose. /** Removes all animations from the track, leaving skeletons in their current pose.
* <p> * <p>
* It may be desired to use {@link AnimationState#setEmptyAnimation(int, float)} to mix the skeletons back to the setup pose, * It may be desired to use {@link AnimationState#setEmptyAnimation(int, float)} to mix the skeletons back to the setup pose,
* rather than leaving them in their previous pose. */ * rather than leaving them in their current pose. */
public void clearTrack (int trackIndex) { public void clearTrack (int trackIndex) {
if (trackIndex >= tracks.size) return; if (trackIndex >= tracks.size) return;
TrackEntry current = tracks.get(trackIndex); TrackEntry current = tracks.get(trackIndex);
@ -534,9 +527,10 @@ public class AnimationState {
/** Adds an animation to be played after the current or last queued animation for a track. If the track is empty, it is /** Adds an animation to be played after the current or last queued animation for a track. If the track is empty, it is
* equivalent to calling {@link #setAnimation(int, Animation, boolean)}. * equivalent to calling {@link #setAnimation(int, Animation, boolean)}.
* @param delay Seconds to begin this animation after the start of the previous animation. If <= 0, uses the duration of the * @param delay If > 0, sets {@link TrackEntry#getDelay()}. If <= 0, the delay set is the duration of the previous track entry
* previous track entry minus any mix duration plus the specified <code>delay</code>. If the previous entry is * minus any mix duration (from the {@link AnimationStateData}) plus the specified <code>delay</code> (ie the mix
* looping, its next loop completion is used instead of the duration. * ends at (<code>delay</code> = 0) or before (<code>delay</code> < 0) the previous track entry duration). If the
* previous entry is looping, its next loop completion is used instead of its duration.
* @return A track entry to allow further customization of animation playback. References to the track entry must not be kept * @return A track entry to allow further customization of animation playback. References to the track entry must not be kept
* after the {@link AnimationStateListener#dispose(TrackEntry)} event occurs. */ * after the {@link AnimationStateListener#dispose(TrackEntry)} event occurs. */
public TrackEntry addAnimation (int trackIndex, Animation animation, boolean loop, float delay) { public TrackEntry addAnimation (int trackIndex, Animation animation, boolean loop, float delay) {
@ -598,9 +592,10 @@ public class AnimationState {
* {@link #setEmptyAnimation(int, float)}. * {@link #setEmptyAnimation(int, float)}.
* <p> * <p>
* See {@link #setEmptyAnimation(int, float)}. * See {@link #setEmptyAnimation(int, float)}.
* @param delay Seconds to begin this animation after the start of the previous animation. If <= 0, uses the duration of the * @param delay If > 0, sets {@link TrackEntry#getDelay()}. If <= 0, the delay set is the duration of the previous track entry
* previous track entry minus any mix duration plus the specified <code>delay</code>. If the previous entry is * minus any mix duration plus the specified <code>delay</code> (ie the mix ends at (<code>delay</code> = 0) or
* looping, its next loop completion is used instead of the duration. * before (<code>delay</code> < 0) the previous track entry duration). If the previous entry is looping, its next
* loop completion is used instead of its duration.
* @return A track entry to allow further customization of animation playback. References to the track entry must not be kept * @return A track entry to allow further customization of animation playback. References to the track entry must not be kept
* after the {@link AnimationStateListener#dispose(TrackEntry)} event occurs. */ * after the {@link AnimationStateListener#dispose(TrackEntry)} event occurs. */
public TrackEntry addEmptyAnimation (int trackIndex, float mixDuration, float delay) { public TrackEntry addEmptyAnimation (int trackIndex, float mixDuration, float delay) {
@ -764,8 +759,8 @@ public class AnimationState {
queue.clear(); queue.clear();
} }
/** Multiplier for the delta time when the animation state is updated, causing time for all animations to play slower or /** Multiplier for the delta time when the animation state is updated, causing time for all animations and mixes to play slower
* faster. Defaults to 1. * or faster. Defaults to 1.
* <p> * <p>
* See TrackEntry {@link TrackEntry#getTimeScale()} for affecting a single animation. */ * See TrackEntry {@link TrackEntry#getTimeScale()} for affecting a single animation. */
public float getTimeScale () { public float getTimeScale () {
@ -859,9 +854,12 @@ public class AnimationState {
this.loop = loop; this.loop = loop;
} }
/** Seconds to postpone playing the animation. When a track entry is the current track entry, <code>delay</code> postpones /** Seconds to postpone playing the animation. When this track entry is the current track entry, <code>delay</code>
* incrementing the {@link #getTrackTime()}. When a track entry is queued, <code>delay</code> is the time from the start of * postpones incrementing the {@link #getTrackTime()}. When this track entry is queued, <code>delay</code> is the time from
* the previous animation to when the track entry will become the current track entry. */ * the start of the previous animation to when this track entry will become the current track entry (ie when the previous
* track entry {@link TrackEntry#getTrackTime()} >= this track entry's <code>delay</code>).
* <p>
* {@link #getTimeScale()} affects the delay. */
public float getDelay () { public float getDelay () {
return delay; return delay;
} }
@ -943,10 +941,15 @@ public class AnimationState {
return Math.min(trackTime + animationStart, animationEnd); return Math.min(trackTime + animationStart, animationEnd);
} }
/** Multiplier for the delta time when the animation state is updated, causing time for this animation to pass slower or /** Multiplier for the delta time when this track entry is updated, causing time for this animation to pass slower or
* faster. Defaults to 1. * faster. Defaults to 1.
* <p> * <p>
* If <code>timeScale</code> is 0, any {@link #getMixDuration()} will be ignored. * {@link #getMixTime()} is not affected by track entry time scale, so {@link #getMixDuration()} may need to be adjusted to
* match the animation speed.
* <p>
* When using {@link AnimationState#addAnimation(int, Animation, boolean, float)} with a <code>delay</code> <= 0, note the
* {@link #getDelay()} is set using the mix duration from the {@link AnimationStateData}, assuming time scale to be 1. If
* the time scale is not 1, the delay may need to be adjusted.
* <p> * <p>
* See AnimationState {@link AnimationState#getTimeScale()} for affecting all animations. */ * See AnimationState {@link AnimationState#getTimeScale()} for affecting all animations. */
public float getTimeScale () { public float getTimeScale () {
@ -970,11 +973,11 @@ public class AnimationState {
this.listener = listener; this.listener = listener;
} }
/** Values < 1 mix this animation with the setup pose or the skeleton's previous pose. Defaults to 1, which overwrites the /** Values < 1 mix this animation with the skeleton's current pose (usually the pose resulting from lower tracks). Defaults
* skeleton's previous pose with this animation. * to 1, which overwrites the skeleton's current pose with this animation.
* <p> * <p>
* Typically track 0 is used to completely pose the skeleton, then alpha can be used on higher tracks. It doesn't make sense * Typically track 0 is used to completely pose the skeleton, then alpha is used on higher tracks. It doesn't make sense to
* to use alpha on track 0 if the skeleton pose is from the last frame render. */ * use alpha on track 0 if the skeleton pose is from the last frame render. */
public float getAlpha () { public float getAlpha () {
return alpha; return alpha;
} }
@ -984,7 +987,7 @@ public class AnimationState {
} }
/** When the mix percentage ({@link #getMixTime()} / {@link #getMixDuration()}) is less than the /** When the mix percentage ({@link #getMixTime()} / {@link #getMixDuration()}) is less than the
* <code>eventThreshold</code>, event timelines for the animation being mixed out will be applied. Defaults to 0, so event * <code>eventThreshold</code>, event timelines are applied while this animation is being mixed out. Defaults to 0, so event
* timelines are not applied for an animation being mixed out. */ * timelines are not applied for an animation being mixed out. */
public float getEventThreshold () { public float getEventThreshold () {
return eventThreshold; return eventThreshold;
@ -995,8 +998,8 @@ public class AnimationState {
} }
/** When the mix percentage ({@link #getMixTime()} / {@link #getMixDuration()}) is less than the /** When the mix percentage ({@link #getMixTime()} / {@link #getMixDuration()}) is less than the
* <code>attachmentThreshold</code>, attachment timelines for the animation being mixed out will be applied. Defaults to 0, * <code>attachmentThreshold</code>, attachment timelines are applied while this animation is being mixed out. Defaults to
* so attachment timelines are not applied for an animation being mixed out. */ * 0, so attachment timelines are not applied for an animation being mixed out. */
public float getAttachmentThreshold () { public float getAttachmentThreshold () {
return attachmentThreshold; return attachmentThreshold;
} }
@ -1006,7 +1009,7 @@ public class AnimationState {
} }
/** When the mix percentage ({@link #getMixTime()} / {@link #getMixDuration()}) is less than the /** When the mix percentage ({@link #getMixTime()} / {@link #getMixDuration()}) is less than the
* <code>drawOrderThreshold</code>, draw order timelines for the animation being mixed out will be applied. Defaults to 0, * <code>drawOrderThreshold</code>, draw order timelines are applied while this animation is being mixed out. Defaults to 0,
* so draw order timelines are not applied for an animation being mixed out. */ * so draw order timelines are not applied for an animation being mixed out. */
public float getDrawOrderThreshold () { public float getDrawOrderThreshold () {
return drawOrderThreshold; return drawOrderThreshold;
@ -1046,7 +1049,8 @@ public class AnimationState {
* track entry only before {@link AnimationState#update(float)} is first called. * track entry only before {@link AnimationState#update(float)} is first called.
* <p> * <p>
* When using {@link AnimationState#addAnimation(int, Animation, boolean, float)} with a <code>delay</code> <= 0, note the * When using {@link AnimationState#addAnimation(int, Animation, boolean, float)} with a <code>delay</code> <= 0, note the
* {@link #getDelay()} is set using the mix duration from the {@link AnimationStateData}. */ * {@link #getDelay()} is set using the mix duration from the {@link AnimationStateData}, not a mix duration set
* afterward. */
public float getMixDuration () { public float getMixDuration () {
return mixDuration; return mixDuration;
} }

View File

@ -547,11 +547,12 @@ public class Bone implements Updatable {
/** Transforms a world rotation to a local rotation. */ /** Transforms a world rotation to a local rotation. */
public float worldToLocalRotation (float worldRotation) { public float worldToLocalRotation (float worldRotation) {
float sin = sinDeg(worldRotation), cos = cosDeg(worldRotation); float sin = sinDeg(worldRotation), cos = cosDeg(worldRotation);
return atan2(a * sin - c * cos, d * cos - b * sin) * radDeg; return atan2(a * sin - c * cos, d * cos - b * sin) * radDeg + rotation - shearX;
} }
/** Transforms a local rotation to a world rotation. */ /** Transforms a local rotation to a world rotation. */
public float localToWorldRotation (float localRotation) { public float localToWorldRotation (float localRotation) {
localRotation -= rotation - shearX;
float sin = sinDeg(localRotation), cos = cosDeg(localRotation); float sin = sinDeg(localRotation), cos = cosDeg(localRotation);
return atan2(cos * c + sin * d, cos * a + sin * b) * radDeg; return atan2(cos * c + sin * d, cos * a + sin * b) * radDeg;
} }

View File

@ -430,14 +430,23 @@ public class PathConstraint implements Constraint {
private void addCurvePosition (float p, float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2, private void addCurvePosition (float p, float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2,
float[] out, int o, boolean tangents) { float[] out, int o, boolean tangents) {
if (p < epsilon || Float.isNaN(p)) p = epsilon; if (p < epsilon || Float.isNaN(p)) {
out[o] = x1;
out[o + 1] = y1;
out[o + 2] = (float)Math.atan2(cy1 - y1, cx1 - x1);
return;
}
float tt = p * p, ttt = tt * p, u = 1 - p, uu = u * u, uuu = uu * u; float tt = p * p, ttt = tt * p, u = 1 - p, uu = u * u, uuu = uu * u;
float ut = u * p, ut3 = ut * 3, uut3 = u * ut3, utt3 = ut3 * p; float ut = u * p, ut3 = ut * 3, uut3 = u * ut3, utt3 = ut3 * p;
float x = x1 * uuu + cx1 * uut3 + cx2 * utt3 + x2 * ttt, y = y1 * uuu + cy1 * uut3 + cy2 * utt3 + y2 * ttt; float x = x1 * uuu + cx1 * uut3 + cx2 * utt3 + x2 * ttt, y = y1 * uuu + cy1 * uut3 + cy2 * utt3 + y2 * ttt;
out[o] = x; out[o] = x;
out[o + 1] = y; out[o + 1] = y;
if (tangents) if (tangents) {
out[o + 2] = (float)Math.atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt)); if (p < 0.001f)
out[o + 2] = (float)Math.atan2(cy1 - y1, cx1 - x1);
else
out[o + 2] = (float)Math.atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt));
}
} }
public int getOrder () { public int getOrder () {

View File

@ -70,12 +70,12 @@ public class SkeletonRenderer {
* previous blend function is not restored, since that could result in unnecessary flushes, depending on what is rendered * previous blend function is not restored, since that could result in unnecessary flushes, depending on what is rendered
* next. */ * next. */
public void draw (Batch batch, Skeleton skeleton) { public void draw (Batch batch, Skeleton skeleton) {
if (batch instanceof PolygonSpriteBatch) { if (batch instanceof TwoColorPolygonBatch) {
draw((PolygonSpriteBatch)batch, skeleton);
return;
} else if (batch instanceof TwoColorPolygonBatch) {
draw((TwoColorPolygonBatch)batch, skeleton); draw((TwoColorPolygonBatch)batch, skeleton);
return; return;
} else if (batch instanceof PolygonSpriteBatch) {
draw((PolygonSpriteBatch)batch, skeleton);
return;
} }
VertexEffect vertexEffect = this.vertexEffect; VertexEffect vertexEffect = this.vertexEffect;
@ -357,7 +357,7 @@ public class SkeletonRenderer {
FloatArray clippedVertices = clipper.getClippedVertices(); FloatArray clippedVertices = clipper.getClippedVertices();
ShortArray clippedTriangles = clipper.getClippedTriangles(); ShortArray clippedTriangles = clipper.getClippedTriangles();
if (vertexEffect != null) applyVertexEffect(clippedVertices.items, clippedVertices.size, 6, light, dark); if (vertexEffect != null) applyVertexEffect(clippedVertices.items, clippedVertices.size, 6, light, dark);
batch.draw(texture, clippedVertices.items, 0, clippedVertices.size, clippedTriangles.items, 0, batch.drawTwoColor(texture, clippedVertices.items, 0, clippedVertices.size, clippedTriangles.items, 0,
clippedTriangles.size); clippedTriangles.size);
} else { } else {
if (vertexEffect != null) { if (vertexEffect != null) {
@ -386,7 +386,7 @@ public class SkeletonRenderer {
vertices[v + 3] = uvs[u + 1]; vertices[v + 3] = uvs[u + 1];
} }
} }
batch.draw(texture, vertices, 0, verticesLength, triangles, 0, triangles.length); batch.drawTwoColor(texture, vertices, 0, verticesLength, triangles, 0, triangles.length);
} }
} }

View File

@ -31,10 +31,15 @@
package com.esotericsoftware.spine.utils; package com.esotericsoftware.spine.utils;
import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.*; import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Mesh;
import com.badlogic.gdx.graphics.Mesh.VertexDataType; import com.badlogic.gdx.graphics.Mesh.VertexDataType;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.VertexAttribute;
import com.badlogic.gdx.graphics.VertexAttributes.Usage; import com.badlogic.gdx.graphics.VertexAttributes.Usage;
import com.badlogic.gdx.graphics.g2d.Batch; import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.graphics.g2d.PolygonBatch;
import com.badlogic.gdx.graphics.g2d.PolygonRegion; import com.badlogic.gdx.graphics.g2d.PolygonRegion;
import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.graphics.glutils.ShaderProgram; import com.badlogic.gdx.graphics.glutils.ShaderProgram;
@ -43,13 +48,18 @@ import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Matrix4; import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.utils.NumberUtils; import com.badlogic.gdx.utils.NumberUtils;
public class TwoColorPolygonBatch implements Batch { /** A batch that renders polygons and performs tinting using a light and dark color.
* <p>
* Because an additional vertex attribute is used, the {@link Batch} and {@link PolygonBatch} methods that accept float[] vertex
* data do not perform two color tinting. {@link #drawTwoColor(Texture, float[], int, int)} and
* {@link #drawTwoColor(Texture, float[], int, int, short[], int, int)} are provided to accept float[] vertex data that contains
* two colors per vertex. */
public class TwoColorPolygonBatch implements PolygonBatch {
static final int VERTEX_SIZE = 2 + 1 + 1 + 2; static final int VERTEX_SIZE = 2 + 1 + 1 + 2;
static final int SPRITE_SIZE = 4 * VERTEX_SIZE; static final int SPRITE_SIZE = 4 * VERTEX_SIZE;
private final Mesh mesh; private final Mesh mesh;
private final float[] vertices; private final float[] vertices;
private final float[] tempSpriteVertices = new float[SPRITE_SIZE];
private final short[] triangles; private final short[] triangles;
private final Matrix4 transformMatrix = new Matrix4(); private final Matrix4 transformMatrix = new Matrix4();
private final Matrix4 projectionMatrix = new Matrix4(); private final Matrix4 projectionMatrix = new Matrix4();
@ -313,30 +323,6 @@ public class TwoColorPolygonBatch implements Batch {
this.vertexIndex = vertexIndex; this.vertexIndex = vertexIndex;
} }
public void draw (Texture texture, float[] polygonVertices, int verticesOffset, int verticesCount, short[] polygonTriangles,
int trianglesOffset, int trianglesCount) {
if (!drawing) throw new IllegalStateException("begin must be called before draw.");
final short[] triangles = this.triangles;
final float[] vertices = this.vertices;
if (texture != lastTexture) {
switchTexture(texture);
} else if (triangleIndex + trianglesCount > triangles.length || vertexIndex + verticesCount > vertices.length) //
flush();
int triangleIndex = this.triangleIndex;
final int vertexIndex = this.vertexIndex;
final int startVertex = vertexIndex / 6;
for (int i = trianglesOffset, n = i + trianglesCount; i < n; i++)
triangles[triangleIndex++] = (short)(polygonTriangles[i] + startVertex);
this.triangleIndex = triangleIndex;
System.arraycopy(polygonVertices, verticesOffset, vertices, vertexIndex, verticesCount);
this.vertexIndex += verticesCount;
}
@Override @Override
public void draw (Texture texture, float x, float y, float originX, float originY, float width, float height, float scaleX, public void draw (Texture texture, float x, float y, float originX, float originY, float width, float height, float scaleX,
float scaleY, float rotation, int srcX, int srcY, int srcWidth, int srcHeight, boolean flipX, boolean flipY) { float scaleY, float rotation, int srcX, int srcY, int srcWidth, int srcHeight, boolean flipX, boolean flipY) {
@ -746,28 +732,74 @@ public class TwoColorPolygonBatch implements Batch {
this.vertexIndex = idx; this.vertexIndex = idx;
} }
/** Draws a rectangle using the given vertices. There must be 4 vertices, each made up of 6 elements in this order: x, y, /** Draws polygons using the given vertices and triangles. There must be 4 vertices, each made up of 6 elements in this order:
* lightColor, darkColor, u, v. The {@link #getColor()} and {@link #getDarkColor()} from the TwoColorPolygonBatch is not * x, y, lightColor, darkColor, u, v. The {@link #getColor()} and {@link #getDarkColor()} from the TwoColorPolygonBatch is not
* applied. */ * applied. */
@Override public void drawTwoColor (Texture texture, float[] polygonVertices, int verticesOffset, int verticesCount,
public void draw (Texture texture, float[] spriteVertices, int offset, int count) { short[] polygonTriangles, int trianglesOffset, int trianglesCount) {
if (!drawing) throw new IllegalStateException("begin must be called before draw."); if (!drawing) throw new IllegalStateException("begin must be called before draw.");
// odds are this is a sprite, we meed to convert it final short[] triangles = this.triangles;
if (spriteVertices.length == 20 && offset == 0 && count == 20) { final float[] vertices = this.vertices;
final float[] vertices = tempSpriteVertices;
int idx = 0; if (texture != lastTexture) {
for (int i = 0; i < 20; i += 5) { switchTexture(texture);
vertices[idx++] = spriteVertices[i]; } else if (triangleIndex + trianglesCount > triangles.length || vertexIndex + verticesCount > vertices.length) //
vertices[idx++] = spriteVertices[i + 1]; flush();
vertices[idx++] = spriteVertices[i + 2];
vertices[idx++] = 0; // dark int triangleIndex = this.triangleIndex;
vertices[idx++] = spriteVertices[i + 3]; final int vertexIndex = this.vertexIndex;
vertices[idx++] = spriteVertices[i + 4]; final int startVertex = vertexIndex / 6;
}
spriteVertices = vertices; for (int i = trianglesOffset, n = i + trianglesCount; i < n; i++)
count = SPRITE_SIZE; triangles[triangleIndex++] = (short)(polygonTriangles[i] + startVertex);
this.triangleIndex = triangleIndex;
System.arraycopy(polygonVertices, verticesOffset, vertices, vertexIndex, verticesCount);
this.vertexIndex += verticesCount;
}
/** Draws polygons using the given vertices and triangles in the {@link PolygonBatch} format. There must be 4 vertices, each
* made up of 5 elements in this order: x, y, color, u, v. The {@link #getColor()} and {@link #getDarkColor()} from the
* TwoColorPolygonBatch is not applied. */
public void draw (Texture texture, float[] polygonVertices, int verticesOffset, int verticesCount, short[] polygonTriangles,
int trianglesOffset, int trianglesCount) {
if (!drawing) throw new IllegalStateException("begin must be called before draw.");
final short[] triangles = this.triangles;
final float[] vertices = this.vertices;
if (texture != lastTexture) {
switchTexture(texture);
} else if (triangleIndex + trianglesCount > triangles.length || vertexIndex + verticesCount / 5 * 6 > vertices.length) //
flush();
int triangleIndex = this.triangleIndex;
final int vertexIndex = this.vertexIndex;
final int startVertex = vertexIndex / 6;
for (int i = trianglesOffset, n = i + trianglesCount; i < n; i++)
triangles[triangleIndex++] = (short)(polygonTriangles[i] + startVertex);
this.triangleIndex = triangleIndex;
int idx = this.vertexIndex;
for (int i = verticesOffset, n = verticesOffset + verticesCount; i < n; i += 5) {
vertices[idx++] = polygonVertices[i];
vertices[idx++] = polygonVertices[i + 1];
vertices[idx++] = polygonVertices[i + 2];
vertices[idx++] = 0; // dark
vertices[idx++] = polygonVertices[i + 3];
vertices[idx++] = polygonVertices[i + 4];
} }
this.vertexIndex = idx;
}
/** Draws rectangles using the given vertices. There must be 4 vertices, each made up of 6 elements in this order: x, y,
* lightColor, darkColor, u, v. The {@link #getColor()} and {@link #getDarkColor()} from the TwoColorPolygonBatch is not
* applied. */
public void drawTwoColor (Texture texture, float[] spriteVertices, int offset, int count) {
if (!drawing) throw new IllegalStateException("begin must be called before draw.");
final short[] triangles = this.triangles; final short[] triangles = this.triangles;
final float[] vertices = this.vertices; final float[] vertices = this.vertices;
@ -794,6 +826,47 @@ public class TwoColorPolygonBatch implements Batch {
this.vertexIndex += count; this.vertexIndex += count;
} }
/** Draws rectangles using the given vertices in the {@link Batch} format. There must be 4 vertices, each made up of 5 elements
* in this order: x, y, color, u, v. The {@link #getColor()} and {@link #getDarkColor()} from the TwoColorPolygonBatch is not
* applied. */
@Override
public void draw (Texture texture, float[] spriteVertices, int offset, int count) {
if (!drawing) throw new IllegalStateException("begin must be called before draw.");
final short[] triangles = this.triangles;
final float[] vertices = this.vertices;
final int triangleCount = count / 20 * 6;
if (texture != lastTexture)
switchTexture(texture);
else if (triangleIndex + triangleCount > triangles.length || vertexIndex + count / 5 * 6 > vertices.length) //
flush();
final int vertexIndex = this.vertexIndex;
int triangleIndex = this.triangleIndex;
short vertex = (short)(vertexIndex / VERTEX_SIZE);
for (int n = triangleIndex + triangleCount; triangleIndex < n; triangleIndex += 6, vertex += 4) {
triangles[triangleIndex] = vertex;
triangles[triangleIndex + 1] = (short)(vertex + 1);
triangles[triangleIndex + 2] = (short)(vertex + 2);
triangles[triangleIndex + 3] = (short)(vertex + 2);
triangles[triangleIndex + 4] = (short)(vertex + 3);
triangles[triangleIndex + 5] = vertex;
}
this.triangleIndex = triangleIndex;
int idx = this.vertexIndex;
for (int i = offset, n = offset + count; i < n; i += 5) {
vertices[idx++] = spriteVertices[i];
vertices[idx++] = spriteVertices[i + 1];
vertices[idx++] = spriteVertices[i + 2];
vertices[idx++] = 0; // dark
vertices[idx++] = spriteVertices[i + 3];
vertices[idx++] = spriteVertices[i + 4];
}
this.vertexIndex = idx;
}
@Override @Override
public void draw (TextureRegion region, float x, float y) { public void draw (TextureRegion region, float x, float y) {
draw(region, x, y, region.getRegionWidth(), region.getRegionHeight()); draw(region, x, y, region.getRegionWidth(), region.getRegionHeight());

View File

@ -244,6 +244,10 @@ namespace Spine.Unity.Editor {
const string DEFAULT_ZSPACING_KEY = "SPINE_DEFAULT_ZSPACING"; const string DEFAULT_ZSPACING_KEY = "SPINE_DEFAULT_ZSPACING";
public static float defaultZSpacing = DEFAULT_DEFAULT_ZSPACING; public static float defaultZSpacing = DEFAULT_DEFAULT_ZSPACING;
const bool DEFAULT_DEFAULT_INSTANTIATE_LOOP = true;
const string DEFAULT_INSTANTIATE_LOOP_KEY = "SPINE_DEFAULT_INSTANTIATE_LOOP";
public static bool defaultInstantiateLoop = DEFAULT_DEFAULT_INSTANTIATE_LOOP;
const bool DEFAULT_SHOW_HIERARCHY_ICONS = true; const bool DEFAULT_SHOW_HIERARCHY_ICONS = true;
const string SHOW_HIERARCHY_ICONS_KEY = "SPINE_SHOW_HIERARCHY_ICONS"; const string SHOW_HIERARCHY_ICONS_KEY = "SPINE_SHOW_HIERARCHY_ICONS";
public static bool showHierarchyIcons = DEFAULT_SHOW_HIERARCHY_ICONS; public static bool showHierarchyIcons = DEFAULT_SHOW_HIERARCHY_ICONS;
@ -327,6 +331,11 @@ namespace Spine.Unity.Editor {
if (EditorGUI.EndChangeCheck()) if (EditorGUI.EndChangeCheck())
EditorPrefs.SetFloat(DEFAULT_ZSPACING_KEY, defaultZSpacing); EditorPrefs.SetFloat(DEFAULT_ZSPACING_KEY, defaultZSpacing);
EditorGUI.BeginChangeCheck();
defaultInstantiateLoop = EditorGUILayout.Toggle(new GUIContent("Default Loop", "Spawn Spine GameObjects with loop enabled."), defaultInstantiateLoop);
if (EditorGUI.EndChangeCheck())
EditorPrefs.SetBool(DEFAULT_INSTANTIATE_LOOP_KEY, defaultInstantiateLoop);
EditorGUILayout.Space(); EditorGUILayout.Space();
EditorGUILayout.LabelField("Handles and Gizmos", EditorStyles.boldLabel); EditorGUILayout.LabelField("Handles and Gizmos", EditorStyles.boldLabel);
EditorGUI.BeginChangeCheck(); EditorGUI.BeginChangeCheck();
@ -1246,6 +1255,7 @@ namespace Spine.Unity.Editor {
throw e; throw e;
} }
newSkeletonAnimation.loop = SpineEditorUtilities.Preferences.defaultInstantiateLoop;
newSkeletonAnimation.skeleton.Update(0); newSkeletonAnimation.skeleton.Update(0);
newSkeletonAnimation.state.Update(0); newSkeletonAnimation.state.Update(0);
newSkeletonAnimation.state.Apply(newSkeletonAnimation.skeleton); newSkeletonAnimation.state.Apply(newSkeletonAnimation.skeleton);

View File

@ -195,6 +195,7 @@ namespace Spine.Unity.Editor {
skin = skin ?? data.DefaultSkin ?? data.Skins.Items[0]; skin = skin ?? data.DefaultSkin ?? data.Skins.Items[0];
graphic.MeshGenerator.settings.zSpacing = SpineEditorUtilities.Preferences.defaultZSpacing; graphic.MeshGenerator.settings.zSpacing = SpineEditorUtilities.Preferences.defaultZSpacing;
graphic.startingLoop = SpineEditorUtilities.Preferences.defaultInstantiateLoop;
graphic.Initialize(false); graphic.Initialize(false);
if (skin != null) graphic.Skeleton.SetSkin(skin); if (skin != null) graphic.Skeleton.SetSkin(skin);
graphic.initialSkinName = skin.Name; graphic.initialSkinName = skin.Name;

View File

@ -258,12 +258,12 @@ namespace Spine.Unity.Editor {
GameObject go = skeletonUtility.SpawnBoneRecursively(bone, utilityBone.transform, utilityBone.mode, utilityBone.position, utilityBone.rotation, utilityBone.scale); GameObject go = skeletonUtility.SpawnBoneRecursively(bone, utilityBone.transform, utilityBone.mode, utilityBone.position, utilityBone.rotation, utilityBone.scale);
SkeletonUtilityBone[] newUtilityBones = go.GetComponentsInChildren<SkeletonUtilityBone>(); SkeletonUtilityBone[] newUtilityBones = go.GetComponentsInChildren<SkeletonUtilityBone>();
foreach (SkeletonUtilityBone utilBone in newUtilityBones) foreach (SkeletonUtilityBone utilBone in newUtilityBones)
SkeletonGameObjectsInspector.AttachIcon(utilBone); SkeletonUtilityInspector.AttachIcon(utilBone);
} }
} else { } else {
var bone = (Bone)obj; var bone = (Bone)obj;
GameObject go = skeletonUtility.SpawnBone(bone, utilityBone.transform, utilityBone.mode, utilityBone.position, utilityBone.rotation, utilityBone.scale); GameObject go = skeletonUtility.SpawnBone(bone, utilityBone.transform, utilityBone.mode, utilityBone.position, utilityBone.rotation, utilityBone.scale);
SkeletonGameObjectsInspector.AttachIcon(go.GetComponent<SkeletonUtilityBone>()); SkeletonUtilityInspector.AttachIcon(go.GetComponent<SkeletonUtilityBone>());
Selection.activeGameObject = go; Selection.activeGameObject = go;
EditorGUIUtility.PingObject(go); EditorGUIUtility.PingObject(go);
} }
@ -272,7 +272,7 @@ namespace Spine.Unity.Editor {
void SpawnOverride () { void SpawnOverride () {
GameObject go = skeletonUtility.SpawnBone(utilityBone.bone, utilityBone.transform.parent, SkeletonUtilityBone.Mode.Override, utilityBone.position, utilityBone.rotation, utilityBone.scale); GameObject go = skeletonUtility.SpawnBone(utilityBone.bone, utilityBone.transform.parent, SkeletonUtilityBone.Mode.Override, utilityBone.position, utilityBone.rotation, utilityBone.scale);
go.name = go.name + " [Override]"; go.name = go.name + " [Override]";
SkeletonGameObjectsInspector.AttachIcon(go.GetComponent<SkeletonUtilityBone>()); SkeletonUtilityInspector.AttachIcon(go.GetComponent<SkeletonUtilityBone>());
Selection.activeGameObject = go; Selection.activeGameObject = go;
EditorGUIUtility.PingObject(go); EditorGUIUtility.PingObject(go);
} }

View File

@ -41,17 +41,17 @@ namespace Spine.Unity.Editor {
using Icons = SpineEditorUtilities.Icons; using Icons = SpineEditorUtilities.Icons;
[CustomEditor(typeof(SkeletonUtility))] [CustomEditor(typeof(SkeletonUtility))]
public class SkeletonGameObjectsInspector : UnityEditor.Editor { public class SkeletonUtilityInspector : UnityEditor.Editor {
SkeletonUtility skeletonGameObjects; SkeletonUtility skeletonUtility;
Skeleton skeleton; Skeleton skeleton;
SkeletonRenderer skeletonRenderer; SkeletonRenderer skeletonRenderer;
bool isPrefab; bool isPrefab;
readonly GUIContent SpawnHierarchyButtonLabel = new GUIContent("Spawn Hierarchy", Icons.skeleton); readonly GUIContent SpawnHierarchyButtonLabel = new GUIContent("Spawn Hierarchy", Icons.skeleton);
void OnEnable () { void OnEnable () {
skeletonGameObjects = (SkeletonUtility)target; skeletonUtility = (SkeletonUtility)target;
skeletonRenderer = skeletonGameObjects.GetComponent<SkeletonRenderer>(); skeletonRenderer = skeletonUtility.GetComponent<SkeletonRenderer>();
skeleton = skeletonRenderer.Skeleton; skeleton = skeletonRenderer.Skeleton;
if (skeleton == null) { if (skeleton == null) {
@ -78,7 +78,7 @@ namespace Spine.Unity.Editor {
EditorGUILayout.PropertyField(serializedObject.FindProperty("boneRoot"), SpineInspectorUtility.TempContent("Skeleton Root")); EditorGUILayout.PropertyField(serializedObject.FindProperty("boneRoot"), SpineInspectorUtility.TempContent("Skeleton Root"));
bool hasRootBone = skeletonGameObjects.boneRoot != null; bool hasRootBone = skeletonUtility.boneRoot != null;
if (!hasRootBone) if (!hasRootBone)
EditorGUILayout.HelpBox("No hierarchy found. Use Spawn Hierarchy to generate GameObjects for bones.", MessageType.Info); EditorGUILayout.HelpBox("No hierarchy found. Use Spawn Hierarchy to generate GameObjects for bones.", MessageType.Info);
@ -90,9 +90,9 @@ namespace Spine.Unity.Editor {
if (hasRootBone) { if (hasRootBone) {
if (SpineInspectorUtility.CenteredButton(new GUIContent("Remove Hierarchy"))) { if (SpineInspectorUtility.CenteredButton(new GUIContent("Remove Hierarchy"))) {
Undo.RegisterCompleteObjectUndo(skeletonGameObjects, "Remove Hierarchy"); Undo.RegisterCompleteObjectUndo(skeletonUtility, "Remove Hierarchy");
Undo.DestroyObjectImmediate(skeletonGameObjects.boneRoot.gameObject); Undo.DestroyObjectImmediate(skeletonUtility.boneRoot.gameObject);
skeletonGameObjects.boneRoot = null; skeletonUtility.boneRoot = null;
} }
} }
} }
@ -134,23 +134,23 @@ namespace Spine.Unity.Editor {
} }
void SpawnFollowHierarchy () { void SpawnFollowHierarchy () {
Selection.activeGameObject = skeletonGameObjects.SpawnHierarchy(SkeletonUtilityBone.Mode.Follow, true, true, true); Selection.activeGameObject = skeletonUtility.SpawnHierarchy(SkeletonUtilityBone.Mode.Follow, true, true, true);
AttachIconsToChildren(skeletonGameObjects.boneRoot); AttachIconsToChildren(skeletonUtility.boneRoot);
} }
void SpawnFollowHierarchyRootOnly () { void SpawnFollowHierarchyRootOnly () {
Selection.activeGameObject = skeletonGameObjects.SpawnRoot(SkeletonUtilityBone.Mode.Follow, true, true, true); Selection.activeGameObject = skeletonUtility.SpawnRoot(SkeletonUtilityBone.Mode.Follow, true, true, true);
AttachIconsToChildren(skeletonGameObjects.boneRoot); AttachIconsToChildren(skeletonUtility.boneRoot);
} }
void SpawnOverrideHierarchy () { void SpawnOverrideHierarchy () {
Selection.activeGameObject = skeletonGameObjects.SpawnHierarchy(SkeletonUtilityBone.Mode.Override, true, true, true); Selection.activeGameObject = skeletonUtility.SpawnHierarchy(SkeletonUtilityBone.Mode.Override, true, true, true);
AttachIconsToChildren(skeletonGameObjects.boneRoot); AttachIconsToChildren(skeletonUtility.boneRoot);
} }
void SpawnOverrideHierarchyRootOnly () { void SpawnOverrideHierarchyRootOnly () {
Selection.activeGameObject = skeletonGameObjects.SpawnRoot(SkeletonUtilityBone.Mode.Override, true, true, true); Selection.activeGameObject = skeletonUtility.SpawnRoot(SkeletonUtilityBone.Mode.Override, true, true, true);
AttachIconsToChildren(skeletonGameObjects.boneRoot); AttachIconsToChildren(skeletonUtility.boneRoot);
} }
} }

View File

@ -172,6 +172,13 @@ namespace Spine.Unity {
if (skeleton != null) skeleton.SetToSetupPose(); if (skeleton != null) skeleton.SetToSetupPose();
} }
/// <summary>
/// Sets a minimum buffer size for the internal MeshGenerator to prevent excess allocations during animation.
/// </summary>
public void EnsureMeshGeneratorCapacity (int minimumVertexCount) {
meshGenerator.EnsureVertexCapacity(minimumVertexCount);
}
/// <summary> /// <summary>
/// Initialize this component. Attempts to load the SkeletonData and creates the internal Skeleton object and buffers.</summary> /// Initialize this component. Attempts to load the SkeletonData and creates the internal Skeleton object and buffers.</summary>
/// <param name="overwrite">If set to <c>true</c>, it will overwrite internal objects if they were already generated. Otherwise, the initialized component will ignore subsequent calls to initialize.</param> /// <param name="overwrite">If set to <c>true</c>, it will overwrite internal objects if they were already generated. Otherwise, the initialized component will ignore subsequent calls to initialize.</param>

View File

@ -455,12 +455,13 @@ namespace Spine.Unity {
public void AddSubmesh (SubmeshInstruction instruction, bool updateTriangles = true) { public void AddSubmesh (SubmeshInstruction instruction, bool updateTriangles = true) {
var settings = this.settings; var settings = this.settings;
if (submeshes.Count - 1 < submeshIndex) { int newSubmeshCount = submeshIndex + 1;
submeshes.Resize(submeshIndex + 1); if (submeshes.Items.Length < newSubmeshCount)
if (submeshes.Items[submeshIndex] == null) submeshes.Resize(newSubmeshCount);
submeshes.Items[submeshIndex] = new ExposedList<int>(); submeshes.Count = newSubmeshCount;
}
var submesh = submeshes.Items[submeshIndex]; var submesh = submeshes.Items[submeshIndex];
if (submesh == null)
submeshes.Items[submeshIndex] = submesh = new ExposedList<int>();
submesh.Clear(false); submesh.Clear(false);
var skeleton = instruction.skeleton; var skeleton = instruction.skeleton;
@ -570,10 +571,13 @@ namespace Spine.Unity {
// Add data to vertex buffers // Add data to vertex buffers
{ {
int newVertexCount = ovc + attachmentVertexCount; int newVertexCount = ovc + attachmentVertexCount;
if (newVertexCount > vertexBuffer.Items.Length) { // Manual ExposedList.Resize() int oldArraySize = vertexBuffer.Items.Length;
Array.Resize(ref vertexBuffer.Items, newVertexCount); if (newVertexCount > oldArraySize) {
Array.Resize(ref uvBuffer.Items, newVertexCount); int newArraySize = (int)(oldArraySize * 1.3f);
Array.Resize(ref colorBuffer.Items, newVertexCount); if (newArraySize < newVertexCount) newArraySize = newVertexCount;
Array.Resize(ref vertexBuffer.Items, newArraySize);
Array.Resize(ref uvBuffer.Items, newArraySize);
Array.Resize(ref colorBuffer.Items, newArraySize);
} }
vertexBuffer.Count = uvBuffer.Count = colorBuffer.Count = newVertexCount; vertexBuffer.Count = uvBuffer.Count = colorBuffer.Count = newVertexCount;
} }
@ -1057,6 +1061,38 @@ namespace Spine.Unity {
} }
#endregion #endregion
public void EnsureVertexCapacity (int minimumVertexCount, bool inlcudeTintBlack = false, bool includeTangents = false, bool includeNormals = false) {
if (minimumVertexCount > vertexBuffer.Items.Length) {
Array.Resize(ref vertexBuffer.Items, minimumVertexCount);
Array.Resize(ref uvBuffer.Items, minimumVertexCount);
Array.Resize(ref colorBuffer.Items, minimumVertexCount);
if (inlcudeTintBlack) {
if (uv2 == null) {
uv2 = new ExposedList<Vector2>(minimumVertexCount);
uv3 = new ExposedList<Vector2>(minimumVertexCount);
}
uv2.Resize(minimumVertexCount);
uv3.Resize(minimumVertexCount);
}
if (includeNormals) {
if (normals == null)
normals = new Vector3[minimumVertexCount];
else
Array.Resize(ref normals, minimumVertexCount);
}
if (includeTangents) {
if (tangents == null)
tangents = new Vector4[minimumVertexCount];
else
Array.Resize(ref tangents, minimumVertexCount);
}
}
}
public void TrimExcess () { public void TrimExcess () {
vertexBuffer.TrimExcess(); vertexBuffer.TrimExcess();
uvBuffer.TrimExcess(); uvBuffer.TrimExcess();
@ -1437,13 +1473,13 @@ namespace Spine.Unity {
this.hasActiveClipping = other.hasActiveClipping; this.hasActiveClipping = other.hasActiveClipping;
this.rawVertexCount = other.rawVertexCount; this.rawVertexCount = other.rawVertexCount;
this.attachments.Clear(false); this.attachments.Clear(false);
this.attachments.GrowIfNeeded(other.attachments.Capacity); this.attachments.EnsureCapacity(other.attachments.Capacity);
this.attachments.Count = other.attachments.Count; this.attachments.Count = other.attachments.Count;
other.attachments.CopyTo(this.attachments.Items); other.attachments.CopyTo(this.attachments.Items);
#endif #endif
this.submeshInstructions.Clear(false); this.submeshInstructions.Clear(false);
this.submeshInstructions.GrowIfNeeded(other.submeshInstructions.Capacity); this.submeshInstructions.EnsureCapacity(other.submeshInstructions.Capacity);
this.submeshInstructions.Count = other.submeshInstructions.Count; this.submeshInstructions.Count = other.submeshInstructions.Count;
other.submeshInstructions.CopyTo(this.submeshInstructions.Items); other.submeshInstructions.CopyTo(this.submeshInstructions.Items);
} }

View File

@ -0,0 +1,105 @@
// - Unlit
// - Premultiplied Alpha Blending (Optional straight alpha input)
// - Double-sided, no depth
Shader "Spine/Special/Skeleton Grayscale" {
Properties {
_GrayPhase ("Phase", Range(0, 1)) = 1
[NoScaleOffset] _MainTex ("MainTex", 2D) = "white" {}
_Cutoff ("Shadow alpha cutoff", Range(0,1)) = 0.1
[Toggle(_STRAIGHT_ALPHA_INPUT)] _StraightAlphaInput("Straight Alpha Texture", Int) = 0
}
SubShader {
Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "PreviewType"="Plane" }
Blend One OneMinusSrcAlpha
Cull Off
ZWrite Off
Lighting Off
Pass {
CGPROGRAM
#pragma shader_feature _ _STRAIGHT_ALPHA_INPUT
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex;
float _GrayPhase;
struct VertexInput {
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float4 vertexColor : COLOR;
};
struct VertexOutput {
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float4 vertexColor : COLOR;
};
VertexOutput vert (VertexInput v) {
VertexOutput o = (VertexOutput)0;
o.uv = v.uv;
o.vertexColor = v.vertexColor;
o.pos = UnityObjectToClipPos(v.vertex);
return o;
}
float4 frag (VertexOutput i) : COLOR {
float4 rawColor = tex2D(_MainTex,i.uv);
float finalAlpha = (rawColor.a * i.vertexColor.a);
#if defined(_STRAIGHT_ALPHA_INPUT)
rawColor.rgb *= rawColor.a;
#endif
rawColor.rgb *= i.vertexColor.rgb;
float3 finalColor = lerp(rawColor.rgb, dot(rawColor.rgb, float3(0.3, 0.59, 0.11)), _GrayPhase);
return fixed4(finalColor, finalAlpha);
}
ENDCG
}
Pass {
Name "Caster"
Tags { "LightMode"="ShadowCaster" }
Offset 1, 1
ZWrite On
ZTest LEqual
Fog { Mode Off }
Cull Off
Lighting Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_shadowcaster
#pragma fragmentoption ARB_precision_hint_fastest
#include "UnityCG.cginc"
sampler2D _MainTex;
fixed _Cutoff;
struct VertexOutput {
V2F_SHADOW_CASTER;
float2 uv : TEXCOORD1;
};
VertexOutput vert (appdata_base v) {
VertexOutput o;
o.uv = v.texcoord;
TRANSFER_SHADOW_CASTER(o)
return o;
}
float4 frag (VertexOutput i) : COLOR {
fixed4 texcol = tex2D(_MainTex, i.uv);
clip(texcol.a - _Cutoff);
SHADOW_CASTER_FRAGMENT(i)
}
ENDCG
}
}
FallBack "Diffuse"
}

View File

@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: ea7e7c05f36541b4bb280f98ebda8ba1
timeCreated: 1492385797
licenseType: Free
ShaderImporter:
defaultTextures: []
userData:
assetBundleName:
assetBundleVariant:

View File

@ -557,7 +557,8 @@ namespace Spine {
var drawOrder = skeleton.drawOrder; var drawOrder = skeleton.drawOrder;
drawOrder.Clear(false); drawOrder.Clear(false);
drawOrder.GrowIfNeeded(n); drawOrder.EnsureCapacity(n);
drawOrder.Count = n;
System.Array.Copy(slotsItems, drawOrder.Items, n); System.Array.Copy(slotsItems, drawOrder.Items, n);
} }