From 51b98ca71e8ba59180a945b2a5e9376554ddffd6 Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Sat, 12 Oct 2013 12:42:47 +0200 Subject: [PATCH] spine-js: keyable draw order, events, bounding boxes. --- spine-js/spine.js | 610 +++++++++++++++++++++++++++++----- spine-js/turbulenz/index.html | 10 +- 2 files changed, 533 insertions(+), 87 deletions(-) diff --git a/spine-js/spine.js b/spine-js/spine.js index 4d13da42f..ba0e09423 100644 --- a/spine-js/spine.js +++ b/spine-js/spine.js @@ -184,17 +184,23 @@ spine.Animation = function (name, timelines, duration) { this.duration = duration; }; spine.Animation.prototype = { - apply: function (skeleton, time, loop) { - if (loop && this.duration != 0) time %= this.duration; + apply: function (skeleton, lastTime, time, loop, events) { + if (loop && this.duration != 0) { + time %= this.duration; + lastTime %= this.duration; + } var timelines = this.timelines; for (var i = 0, n = timelines.length; i < n; i++) - timelines[i].apply(skeleton, time, 1); + timelines[i].apply(skeleton, lastTime, time, events, 1); }, - mix: function (skeleton, time, loop, alpha) { - if (loop && this.duration != 0) time %= this.duration; + mix: function (skeleton, lastTime, time, loop, events, alpha) { + if (loop && this.duration != 0) { + time %= this.duration; + lastTime %= this.duration; + } var timelines = this.timelines; for (var i = 0, n = timelines.length; i < n; i++) - timelines[i].apply(skeleton, time, alpha); + timelines[i].apply(skeleton, lastTime, time, events, alpha); } }; @@ -301,7 +307,7 @@ spine.RotateTimeline.prototype = { this.frames[frameIndex] = time; this.frames[frameIndex + 1] = angle; }, - apply: function (skeleton, time, alpha) { + apply: function (skeleton, lastTime, time, firedEvents, alpha) { var frames = this.frames; if (time < frames[0]) return; // Time is before first frame. @@ -354,7 +360,7 @@ spine.TranslateTimeline.prototype = { this.frames[frameIndex + 1] = x; this.frames[frameIndex + 2] = y; }, - apply: function (skeleton, time, alpha) { + apply: function (skeleton, lastTime, time, firedEvents, alpha) { var frames = this.frames; if (time < frames[0]) return; // Time is before first frame. @@ -395,7 +401,7 @@ spine.ScaleTimeline.prototype = { this.frames[frameIndex + 1] = x; this.frames[frameIndex + 2] = y; }, - apply: function (skeleton, time, alpha) { + apply: function (skeleton, lastTime, time, firedEvents, alpha) { var frames = this.frames; if (time < frames[0]) return; // Time is before first frame. @@ -438,7 +444,7 @@ spine.ColorTimeline.prototype = { this.frames[frameIndex + 3] = b; this.frames[frameIndex + 4] = a; }, - apply: function (skeleton, time, alpha) { + apply: function (skeleton, lastTime, time, firedEvents, alpha) { var frames = this.frames; if (time < frames[0]) return; // Time is before first frame. @@ -485,7 +491,7 @@ spine.AttachmentTimeline = function (frameCount) { this.curves = new spine.Curves(frameCount); this.frames = []; // time, ... this.frames.length = frameCount; - this.attachmentNames = []; // time, ... + this.attachmentNames = []; this.attachmentNames.length = frameCount; }; spine.AttachmentTimeline.prototype = { @@ -497,7 +503,7 @@ spine.AttachmentTimeline.prototype = { this.frames[frameIndex] = time; this.attachmentNames[frameIndex] = attachmentName; }, - apply: function (skeleton, time, alpha) { + apply: function (skeleton, lastTime, time, firedEvents, alpha) { var frames = this.frames; if (time < frames[0]) return; // Time is before first frame. @@ -512,10 +518,92 @@ spine.AttachmentTimeline.prototype = { } }; +spine.EventTimeline = function (frameCount) { + this.frames = []; // time, ... + this.frames.length = frameCount; + this.events = []; + this.events.length = frameCount; +}; +spine.EventTimeline.prototype = { + getFrameCount: function () { + return this.frames.length; + }, + setFrame: function (frameIndex, time, event) { + this.frames[frameIndex] = time; + this.events[frameIndex] = event; + }, + apply: function (skeleton, lastTime, time, firedEvents, alpha) { + if (!firedEvents) return; + + var frames = this.frames; + var frameCount = frames.length; + if (lastTime >= frames[frameCount - 1]) return; // Last time is after last frame. + + if (lastTime > time) { // Fire events after last time for looped animations. + this.apply(skeleton, lastTime, Number.MAX_VALUE, firedEvents, alpha); + lastTime = 0; + } + + var frameIndex; + if (lastTime <= frames[0] || frameCount == 1) + frameIndex = 0; + else { + frameIndex = spine.binarySearch(frames, lastTime, 1); + var frame = frames[frameIndex]; + while (frameIndex > 0) { // Fire multiple events with the same frame. + if (frames[frameIndex - 1] != frame) break; + frameIndex--; + } + } + var events = this.events; + for (; frameIndex < frameCount && time >= frames[frameIndex]; frameIndex++) + firedEvents.push(events[frameIndex]); + } +}; + +spine.DrawOrderTimeline = function (frameCount) { + this.frames = []; // time, ... + this.frames.length = frameCount; + this.drawOrders = []; + this.drawOrders.length = frameCount; +}; +spine.DrawOrderTimeline.prototype = { + getFrameCount: function () { + return this.frames.length; + }, + setFrame: function (frameIndex, time, drawOrder) { + this.frames[frameIndex] = time; + this.drawOrders[frameIndex] = drawOrder; + }, + apply: function (skeleton, lastTime, time, firedEvents, alpha) { + var frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + var frameIndex; + if (time >= frames[frames.length - 1]) // Time is after last frame. + frameIndex = frames.length - 1; + else + frameIndex = spine.binarySearch(frames, time, 1) - 1; + + var drawOrder = skeleton.drawOrder; + var slots = skeleton.slots; + var drawOrderToSetupIndex = this.drawOrders[frameIndex]; + if (!drawOrderToSetupIndex) { + for (var i = 0, n = slots.length; i < n; i++) + drawOrder[i] = slots[i]; + } else { + for (var i = 0, n = drawOrderToSetupIndex.length; i < n; i++) + drawOrder[i] = skeleton.slots[drawOrderToSetupIndex[i]]; + } + + } +}; + spine.SkeletonData = function () { this.bones = []; this.slots = []; this.skins = []; + this.events = []; this.animations = []; }; spine.SkeletonData.prototype = { @@ -557,6 +645,13 @@ spine.SkeletonData.prototype = { return null; }, /** @return May be null. */ + findEvent: function (eventName) { + var events = this.events; + for (var i = 0, n = events.length; i < n; i++) + if (events[i].name == eventName) return events[i]; + return null; + }, + /** @return May be null. */ findAnimation: function (animationName) { var animations = this.animations; for (var i = 0, n = animations.length; i < n; i++) @@ -680,7 +775,7 @@ spine.Skeleton.prototype = { var attachment = null; if (attachmentName) { attachment = this.getAttachment(i, attachmentName); - if (attachment == null) throw "Attachment not found: " + attachmentName + ", for slot: " + slotName; + if (!attachment) throw "Attachment not found: " + attachmentName + ", for slot: " + slotName; } slot.setAttachment(attachment); return; @@ -693,18 +788,38 @@ spine.Skeleton.prototype = { } }; +spine.EventData = function (name) { + this.name = name; +} +spine.EventData.prototype = { + intValue: 0, + floatValue: 0, + stringValue: null +}; + +spine.Event = function (data) { + this.data = data; +} +spine.Event.prototype = { + intValue: 0, + floatValue: 0, + stringValue: null +}; + spine.AttachmentType = { region: 0, boundingbox: 1 }; -spine.RegionAttachment = function () { +spine.RegionAttachment = function (name) { + this.name = name; this.offset = []; this.offset.length = 8; this.uvs = []; this.uvs.length = 8; }; spine.RegionAttachment.prototype = { + type: spine.AttachmentType.region, x: 0, y: 0, rotation: 0, scaleX: 1, scaleY: 1, @@ -782,6 +897,29 @@ spine.RegionAttachment.prototype = { } }; +spine.BoundingBoxAttachment = function (name) { + this.name = name; + this.vertices = []; +}; +spine.BoundingBoxAttachment.prototype = { + type: spine.AttachmentType.boundingBox, + computeWorldVertices: function (x, y, bone, worldVertices) { + x += bone.worldX; + y += bone.worldY; + var m00 = bone.m00; + var m01 = bone.m01; + var m10 = bone.m10; + var m11 = bone.m11; + var vertices = this.vertices; + for (var i = 0, n = vertices.length; i < n; i += 2) { + var px = vertices[i]; + var py = vertices[i + 1]; + worldVertices[i] = px * m00 + py * m01 + x; + worldVertices[i + 1] = px * m10 + py * m11 + y; + } + } +}; + spine.AnimationStateData = function (skeletonData) { this.skeletonData = skeletonData; this.animationToMixTime = {}; @@ -804,104 +942,186 @@ spine.AnimationStateData.prototype = { } }; +spine.TrackEntry = function () {}; +spine.TrackEntry.prototype = { + next: null, previous: null, + animation: null, + loop: false, + delay: 0, time: 0, lastTime: 0, endTime: 0, + timeScale: 1, + mixTime: 0, mixDuration: 0, + onStart: null, onEnd: null, onComplete: null, onEvent: null +} + spine.AnimationState = function (stateData) { this.data = stateData; - this.queue = []; + this.tracks = []; + this.events = []; }; spine.AnimationState.prototype = { - current: null, - previous: null, - currentTime: 0, - previousTime: 0, - currentLoop: false, - previousLoop: false, - mixTime: 0, - mixDuration: 0, + onStart: null, + onEnd: null, + onComplete: null, + onEvent: null, + timeScale: 1, update: function (delta) { - this.currentTime += delta; - this.previousTime += delta; - this.mixTime += delta; - - if (this.queue.length > 0) { - var entry = this.queue[0]; - if (this.currentTime >= entry.delay) { - this._setAnimation(entry.animation, entry.loop); - this.queue.shift(); + delta *= this.timeScale; + for (var i = 0; i < this.tracks.length; i++) { + var current = this.tracks[i]; + if (!current) continue; + + var trackDelta = delta * current.timeScale; + var time = current.time + trackDelta; + var endTime = current.endTime; + + current.time = time; + if (current.previous) { + current.previous.time += trackDelta; + current.mixTime += trackDelta; + } + + // Check if completed the animation or a loop iteration. + if (current.loop ? (current.lastTime % endTime > time % endTime) : (current.lastTime < endTime && time >= endTime)) { + var count = Math.floor(time / endTime); + if (current.onComplete) current.onComplete(i, count); + if (this.onComplete) this.onComplete(i, count); + } + + var next = current.next; + if (next) { + if (time - trackDelta > next.delay) this.setCurrent(i, next); + } else { + // End non-looping animation when it reaches its end time and there is no next entry. + if (!current.loop && current.lastTime >= current.endTime) this.clearTrack(i); } } }, apply: function (skeleton) { - if (!this.current) return; - if (this.previous) { - this.previous.apply(skeleton, this.previousTime, this.previousLoop); - var alpha = this.mixTime / this.mixDuration; - if (alpha >= 1) { - alpha = 1; - this.previous = null; + for (var i = 0; i < this.tracks.length; i++) { + var current = this.tracks[i]; + if (!current) continue; + + this.events.length = 0; + + var time = current.time; + var loop = current.loop; + if (!loop && time > current.endTime) time = current.endTime; + + var previous = current.previous; + if (!previous) + current.animation.apply(skeleton, current.lastTime, time, loop, this.events); + else { + var previousTime = previous.time; + if (!previous.loop && previousTime > previous.endTime) previousTime = previous.endTime; + previous.animation.apply(skeleton, previousTime, previousTime, previous.loop, null); + + var alpha = current.mixTime / current.mixDuration; + if (alpha >= 1) { + alpha = 1; + current.previous = null; + } + current.animation.mix(skeleton, current.lastTime, time, loop, this.events, alpha); } - this.current.mix(skeleton, this.currentTime, this.currentLoop, alpha); - } else - this.current.apply(skeleton, this.currentTime, this.currentLoop); + + for (var ii = 0, nn = this.events.length; ii < nn; ii++) { + var event = this.events[ii]; + if (current.onEvent != null) current.onEvent(i, event); + if (this.onEvent != null) this.onEvent(i, event); + } + + current.lastTime = current.time; + } }, - clearAnimation: function () { - this.previous = null; - this.current = null; - this.queue.length = 0; + clearTracks: function () { + for (var i = 0, n = this.tracks.length; i < n; i++) + this.clearTrack(i); + this.tracks.length = 0; }, - _setAnimation: function (animation, loop) { - this.previous = null; - if (animation && this.current) { - this.mixDuration = this.data.getMix(this.current, animation); - if (this.mixDuration > 0) { - this.mixTime = 0; - this.previous = this.current; - this.previousTime = this.currentTime; - this.previousLoop = this.currentLoop; + clearTrack: function (trackIndex) { + if (trackIndex >= this.tracks.length) return; + var current = this.tracks[trackIndex]; + if (!current) return; + + if (current.onEnd != null) current.onEnd(trackIndex); + if (this.onEnd != null) this.onEnd(trackIndex); + + this.tracks[trackIndex] = null; + }, + _expandToIndex: function (index) { + if (index < this.tracks.length) return this.tracks[index]; + while (index >= this.tracks.length) + this.tracks.push(null); + return null; + }, + setCurrent: function (index, entry) { + var current = this._expandToIndex(index); + if (current) { + current.previous = null; + + if (current.onEnd != null) current.onEnd(index); + if (this.onEnd != null) this.onEnd(index); + + entry.mixDuration = this.data.getMix(current.animation, entry.animation); + if (entry.mixDuration > 0) { + entry.mixTime = 0; + entry.previous = current; } } - this.current = animation; - this.currentLoop = loop; - this.currentTime = 0; + + this.tracks[index] = entry; + + if (entry.onStart != null) entry.onStart(index); + if (this.onStart != null) this.onStart(index); }, - /** @see #setAnimation(Animation, Boolean) */ - setAnimationByName: function (animationName, loop) { + setAnimationByName: function (trackIndex, animationName, loop) { var animation = this.data.skeletonData.findAnimation(animationName); if (!animation) throw "Animation not found: " + animationName; - this.setAnimation(animation, loop); + return this.setAnimation(trackIndex, animation, loop); }, - /** Set the current animation. Any queued animations are cleared and the current animation time is set to 0. - * @param animation May be null. */ - setAnimation: function (animation, loop) { - this.queue.length = 0; - this._setAnimation(animation, loop); + /** Set the current animation. Any queued animations are cleared. */ + setAnimation: function (trackIndex, animation, loop) { + var entry = new spine.TrackEntry(); + entry.animation = animation; + entry.loop = loop; + entry.endTime = animation.duration; + this.setCurrent(trackIndex, entry); + return entry; }, - /** @see #addAnimation(Animation, Boolean, Number) */ - addAnimationByName: function (animationName, loop, delay) { + addAnimationByName: function (trackIndex, animationName, loop, delay) { var animation = this.data.skeletonData.findAnimation(animationName); if (!animation) throw "Animation not found: " + animationName; - this.addAnimation(animation, loop, delay); + return this.addAnimation(trackIndex, animation, loop, delay); }, /** Adds an animation to be played delay seconds after the current or last queued animation. * @param delay May be <= 0 to use duration of previous animation minus any mix duration plus the negative delay. */ - addAnimation: function (animation, loop, delay) { - var entry = {}; + addAnimation: function (trackIndex, animation, loop, delay) { + var entry = new spine.TrackEntry(); entry.animation = animation; entry.loop = loop; - - if (!delay || delay <= 0) { - var previousAnimation = this.queue.length == 0 ? this.current : this.queue[this.queue.length - 1].animation; - if (previousAnimation != null) - delay = previousAnimation.duration - this.data.getMix(previousAnimation, animation) + (delay || 0); + entry.endTime = animation.duration; + + var last = this._expandToIndex(trackIndex); + if (last) { + while (last.next) + last = last.next; + last.next = entry; + } else + this.tracks[trackIndex] = entry; + + if (delay <= 0) { + if (last) + delay += last.endTime - this.data.getMix(last.animation, animation); else delay = 0; } entry.delay = delay; - - this.queue.push(entry); - }, - /** Returns true if no animation is set or if the current time is greater than the animation duration, regardless of looping. */ - isComplete: function () { - return !this.current || this.currentTime >= this.current.duration; + + return entry; + }, + /** May be null. */ + getCurrent: function (trackIndex) { + if (trackIndex >= this.tracks.length) return null; + return this.tracks[trackIndex]; } }; @@ -976,6 +1196,18 @@ spine.SkeletonJson.prototype = { if (skin.name == "default") skeletonData.defaultSkin = skin; } + // Events. + var events = root["events"]; + for (var eventName in events) { + if (!events.hasOwnProperty(eventName)) continue; + var eventMap = events[eventName]; + var eventData = new spine.EventData(eventName); + eventData.intValue = eventMap["int"] || 0; + eventData.floatValue = eventMap["float"] || 0; + eventData.stringValue = eventMap["string"] || null; + skeletonData.events.push(eventData); + } + // Animations. var animations = root["animations"]; for (var animationName in animations) { @@ -1000,6 +1232,10 @@ spine.SkeletonJson.prototype = { attachment.width = (map["width"] || 32) * this.scale; attachment.height = (map["height"] || 32) * this.scale; attachment.updateOffset(); + } else if (type == spine.AttachmentType.boundingBox) { + var vertices = map["vertices"]; + for (var i = 0, n = vertices.length; i < n; i++) + attachment.vertices.push(vertices[i] * scale); } return attachment; @@ -1105,6 +1341,64 @@ spine.SkeletonJson.prototype = { } } + var events = map["events"]; + if (events) { + var timeline = new spine.EventTimeline(events.length); + var frameIndex = 0; + for (var i = 0, n = events.length; i < n; i++) { + var eventMap = events[i]; + var eventData = skeletonData.findEvent(eventMap["name"]); + if (!eventData) throw "Event not found: " + eventMap["name"]; + var event = new spine.Event(eventData); + event.intValue = eventMap.hasOwnProperty("int") ? eventMap["int"] : eventData.intValue; + event.floatValue = eventMap.hasOwnProperty("float") ? eventMap["float"] : eventData.floatValue; + event.stringValue = eventMap.hasOwnProperty("string") ? eventMap["string"] : eventData.stringValue; + timeline.setFrame(frameIndex++, eventMap["time"], event); + } + timelines.push(timeline); + duration = Math.max(duration, timeline.frames[timeline.getFrameCount() - 1]); + } + + var drawOrderValues = map["draworder"]; + if (drawOrderValues) { + var timeline = new spine.DrawOrderTimeline(drawOrderValues.length); + var slotCount = skeletonData.slots.length; + var frameIndex = 0; + for (var i = 0, n = drawOrderValues.length; i < n; i++) { + var drawOrderMap = drawOrderValues[i]; + var drawOrder = null; + if (drawOrderMap["offsets"]) { + drawOrder = []; + drawOrder.length = slotCount; + for (var ii = slotCount - 1; ii >= 0; ii--) + drawOrder[ii] = -1; + var offsets = drawOrderMap["offsets"]; + var unchanged = []; + unchanged.length = slotCount - offsets.length; + var originalIndex = 0, unchangedIndex = 0; + for (var ii = 0, nn = offsets.length; ii < nn; ii++) { + var offsetMap = offsets[ii]; + var slotIndex = skeletonData.findSlotIndex(offsetMap["slot"]); + if (slotIndex == -1) throw "Slot not found: " + offsetMap["slot"]; + // Collect unchanged items. + while (originalIndex != slotIndex) + unchanged[unchangedIndex++] = originalIndex++; + // Set changed items. + drawOrder[originalIndex + offsetMap["offset"]] = originalIndex++; + } + // Collect remaining unchanged items. + while (originalIndex < slotCount) + unchanged[unchangedIndex++] = originalIndex++; + // Fill in unchanged items. + for (var ii = slotCount - 1; ii >= 0; ii--) + if (drawOrder[ii] == -1) drawOrder[ii] = unchanged[--unchangedIndex]; + } + timeline.setFrame(frameIndex++, drawOrderMap["time"], drawOrder); + } + timelines.push(timeline); + duration = Math.max(duration, timeline.frames[timeline.getFrameCount() - 1]); + } + skeletonData.animations.push(new spine.Animation(name, timelines, duration)); } }; @@ -1118,7 +1412,7 @@ spine.SkeletonJson.readCurve = function (timeline, frameIndex, valueMap) { }; spine.SkeletonJson.toColor = function (hexString, colorIndex) { if (hexString.length != 8) throw "Color hexidecimal length must be 8, recieved: " + hexString; - return parseInt(hexString.substring(colorIndex * 2, (colorIndex * 2)+2), 16) / 255; + return parseInt(hexString.substring(colorIndex * 2, (colorIndex * 2) + 2), 16) / 255; }; spine.Atlas = function (atlasText, textureLoader) { @@ -1341,7 +1635,7 @@ spine.AtlasAttachmentLoader.prototype = { newAttachment: function (skin, type, name) { switch (type) { case spine.AttachmentType.boundingbox: - return null; // BOZO - Implement bounding boxes. + return new spine.BoundingBoxAttachment(name); case spine.AttachmentType.region: var region = this.atlas.findRegion(name); if (!region) throw "Region not found in atlas: " + name + " (" + type + ")"; @@ -1359,3 +1653,151 @@ spine.AtlasAttachmentLoader.prototype = { throw "Unknown attachment type: " + type; } }; + +spine.SkeletonBounds = function () { + this.polygonPool = []; + this.polygons = []; + this.boundingBoxes = []; +}; +spine.SkeletonBounds.prototype = { + minX: 0, minY: 0, maxX: 0, maxY: 0, + update: function (skeleton, updateAabb) { + var slots = skeleton.slots; + var slotCount = slots.length; + var x = skeleton.x, y = skeleton.y; + var boundingBoxes = this.boundingBoxes; + var polygonPool = this.polygonPool; + var polygons = this.polygons; + + boundingBoxes.length = 0; + for (var i = 0, n = polygons.length; i < n; i++) + polygonPool.push(polygons[i]); + polygons.length = 0; + + for (var i = 0; i < slotCount; i++) { + var slot = slots[i]; + var boundingBox = slot.attachment; + if (boundingBox.type != spine.AttachmentType.boundingBox) continue; + boundingBoxes.push(boundingBox); + + var poolCount = polygonPool.length; + if (poolCount > 0) { + polygon = polygonPool[poolCount - 1]; + polygonPool.splice(poolCount - 1, 1); + } else + polygon = []; + polygons.push(polygon); + + polygon.length = boundingBox.vertices.length; + boundingBox.computeWorldVertices(x, y, slot.bone, polygon); + } + + if (updateAabb) this.aabbCompute(); + }, + aabbCompute: function () { + var polygons = this.polygons; + var minX = Number.MAX_VALUE, minY = Number.MAX_VALUE, maxX = Number.MIN_VALUE, maxY = Number.MIN_VALUE; + for (var i = 0, n = polygons.length; i < n; i++) { + var vertices = polygons[i]; + for (var ii = 0, nn = vertices.length; ii < nn; ii += 2) { + var x = vertices[ii]; + var y = vertices[ii + 1]; + minX = Math.min(minX, x); + minY = Math.min(minY, y); + maxX = Math.max(maxX, x); + maxY = Math.max(maxY, y); + } + } + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + }, + /** Returns true if the axis aligned bounding box contains the point. */ + aabbContainsPoint: function (x, y) { + return x >= this.minX && x <= this.maxX && y >= this.minY && y <= this.maxY; + }, + /** Returns true if the axis aligned bounding box intersects the line segment. */ + aabbIntersectsSegment: function (x1, y1, x2, y2) { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + if ((x1 <= minX && x2 <= minX) || (y1 <= minY && y2 <= minY) || (x1 >= maxX && x2 >= maxX) || (y1 >= maxY && y2 >= maxY)) + return false; + var m = (y2 - y1) / (x2 - x1); + var y = m * (minX - x1) + y1; + if (y > minY && y < maxY) return true; + y = m * (maxX - x1) + y1; + if (y > minY && y < maxY) return true; + var x = (minY - y1) / m + x1; + if (x > minX && x < maxX) return true; + x = (maxY - y1) / m + x1; + if (x > minX && x < maxX) return true; + return false; + }, + /** Returns true if the axis aligned bounding box intersects the axis aligned bounding box of the specified bounds. */ + aabbIntersectsSkeleton: function (bounds) { + return this.minX < bounds.maxX && this.maxX > bounds.minX && this.minY < bounds.maxY && this.maxY > bounds.minY; + }, + /** Returns the first bounding box attachment that contains the point, or null. When doing many checks, it is usually more + * efficient to only call this method if {@link #aabbContainsPoint(float, float)} returns true. */ + containsPoint: function (x, y) { + var polygons = this.polygons; + for (var i = 0, n = polygons.length; i < n; i++) + if (this.polygonContainsPoint(polygons[i], x, y)) return this.boundingBoxes[i]; + return null; + }, + /** Returns the first bounding box attachment that contains the line segment, or null. When doing many checks, it is usually + * more efficient to only call this method if {@link #aabbIntersectsSegment(float, float, float, float)} returns true. */ + intersectsSegment: function (x1, y1, x2, y2) { + var polygons = this.polygons; + for (var i = 0, n = polygons.length; i < n; i++) + if (polygons[i].intersectsSegment(x1, y1, x2, y2)) return boundingBoxes[i]; + return null; + }, + /** Returns true if the polygon contains the point. */ + polygonContainsPoint: function (polygon, x, y) { + var nn = polygon.length; + var prevIndex = nn - 2; + var inside = false; + for (var ii = 0; ii < nn; ii += 2) { + var vertexY = polygon[ii + 1]; + var prevY = polygon[prevIndex + 1]; + if ((vertexY < y && prevY >= y) || (prevY < y && vertexY >= y)) { + var vertexX = polygon[ii]; + if (vertexX + (y - vertexY) / (prevY - vertexY) * (polygon[prevIndex] - vertexX) < x) inside = !inside; + } + prevIndex = ii; + } + return inside; + }, + /** Returns true if the polygon contains the line segment. */ + intersectsSegment: function (polygon, x1, y1, x2, y2) { + var nn = polygon.length; + var width12 = x1 - x2, height12 = y1 - y2; + var det1 = x1 * y2 - y1 * x2; + var x3 = polygon[nn - 2], y3 = polygon[nn - 1]; + for (var ii = 0; ii < nn; ii += 2) { + var x4 = polygon[ii], y4 = polygon[ii + 1]; + var det2 = x3 * y4 - y3 * x4; + var width34 = x3 - x4, height34 = y3 - y4; + var det3 = width12 * height34 - height12 * width34; + var x = (det1 * width34 - width12 * det2) / det3; + if (((x >= x3 && x <= x4) || (x >= x4 && x <= x3)) && ((x >= x1 && x <= x2) || (x >= x2 && x <= x1))) { + var y = (det1 * height34 - height12 * det2) / det3; + if (((y >= y3 && y <= y4) || (y >= y4 && y <= y3)) && ((y >= y1 && y <= y2) || (y >= y2 && y <= y1))) return true; + } + x3 = x4; + y3 = y4; + } + return false; + }, + getPolygon: function (attachment) { + var index = this.boundingBoxes.indexOf(attachment); + return index == -1 ? null : this.polygons[index]; + }, + getWidth: function () { + return this.maxX - this.minX; + }, + getHeight: function () { + return this.maxY - this.minY; + } +}; diff --git a/spine-js/turbulenz/index.html b/spine-js/turbulenz/index.html index 230bb05b5..0185b9a1b 100644 --- a/spine-js/turbulenz/index.html +++ b/spine-js/turbulenz/index.html @@ -85,11 +85,11 @@ function start () { if (skeletonName == "spineboy") { stateData.setMixByName("walk", "jump", 0.2); stateData.setMixByName("jump", "walk", 0.4); - state.setAnimationByName("walk", true); + state.setAnimationByName(0, "drawOrder", true); canvas.onmousedown = function () { - state.setAnimationByName("jump", false); - state.addAnimationByName("walk", true); + state.setAnimationByName(0, "jump", false); + state.addAnimationByName(0, "walk", true, 0); } } else { skeleton.setSkinByName("goblingirl"); @@ -101,6 +101,10 @@ function start () { skeleton.setSlotsToSetupPose(); } } + + state.onEvent = function (trackIndex, event) { + // alert(trackIndex + " event: " + event.data.name) + } var bgColor = [0.9, 0.9, 0.9, 1.0]; var batch = new SpriteBatch(draw2D);