Merge remote-tracking branch 'origin/master'

This commit is contained in:
NathanSweet 2016-03-05 10:33:08 +01:00
commit ed2e688b59
18 changed files with 760 additions and 91 deletions

View File

@ -17,6 +17,61 @@ The Spine Runtimes are developed with the intent to be used with data exported f
Alternatively, the contents of the `spine-c/src`, `spine-c/include` and `spine-cocos2dx/3/src` directories can be copied into your project. Be sure your header search path will find the contents of the `spine-c/include` and `spine-cocos2dx/3.1/src` directories. Note that the includes use `spine/Xxx.h`, so the `spine` directory cannot be omitted when copying the files.
## Setup for Cocos Studio Users (cocos2d-x-3.10)
1. Download the Spine Runtimes source using [git](https://help.github.com/articles/set-up-git) or by downloading it [as a zip](https://github.com/EsotericSoftware/spine-runtimes/archive/master.zip).
- Extract them somewhere you can find them. You will only need `spine-c` and `spine-cocos2dx`.
1. Download and install Cocos: http://www.cocos2d-x.org/download
- (2016 March 4) Downloading and installing Cocos Studio and cocos2d-x-3.10 includes a Spine runtime in their source files. For instance, if the default install folder is `C:\Cocos\`, you will find an existing spine-c and spine-cocos2dx runtime inside `C:\Cocos\Cocos2d-x\cocos2d-x-3.10\cocos\editor-support\spine`.
- Replace the contents of that folder with the files from the following Spine runtime folders:
- `spine-c/src/spine`
- `spine-c/include/spine`
- `spine-cocos2dx/3/src/spine`
- Since this is the shared version of the runtime Cocos Studio uses for all projects created through it, updating the Spine runtime here also updates the Spine runtime of all those projects.
### Examples
1. Create a **new C++ Cocos Studio project**. Make sure you can find that folder.
1. The `spine-cocos2dx/3/` runtime folder contains a folder named `example`.
- Copy the files in `spine-cocos2dx/3/example/Classes` folder (except `AppDelegate.cpp`, `AppDelegate.h` and `AppMacros.h` into the `Classes` folder of your Cocos Studio project.)
1. Add these files into your C++ Solution. (through your IDE, Visual Studio or Eclipse, etc...)
2. If this is a newly created project, edit the `AppDelegate.cpp`. For this example, we will use RaptorExample.
1. Add `#include "RaptorExample.h"` to the includes near the top of the file.
2. Look for the `bool AppDelegate::applicationDidFinishLaunching()` method and replace it with this. Notice the line where it defines the scene `auto scene = RaptorExample::scene()`:
```cpp
bool AppDelegate::applicationDidFinishLaunching() {
// initialize director
auto director = Director::getInstance();
auto glview = director->getOpenGLView();
if(!glview) {
glview = GLViewImpl::createWithRect("Spine Test", Rect(0, 0, 960, 640));
director->setOpenGLView(glview);
}
director->getOpenGLView()->setDesignResolutionSize(960, 640, ResolutionPolicy::SHOW_ALL);
// turn on display FPS
director->setDisplayStats(true);
// set FPS. the default value is 1.0/60 if you don't call this
director->setAnimationInterval(1.0f / 60);
FileUtils::getInstance()->addSearchPath("res");
// create a scene. it's an autorelease object
auto scene = RaptorExample::scene();
// run
director->runWithScene(scene);
return true;
}
```
You can do the same with `GoblinsExample` and `SpineboyExample`.
## Notes
- Images are premultiplied by cocos2d-x, so the Spine atlas images should *not* use premultiplied alpha.

View File

@ -604,7 +604,7 @@ function Animation.FfdTimeline.new ()
function self:apply (skeleton, lastTime, time, firedEvents, alpha)
local slot = skeleton.slots[self.slotIndex]
if slot.attachment ~= attachment then return end
if slot.attachment ~= self.attachment then return end
local frames = self.frames
if time < frames[0] then return end -- Time is before first frame.
@ -612,7 +612,7 @@ function Animation.FfdTimeline.new ()
local frameVertices = self.frameVertices
local vertexCount = #frameVertices[0]
local vertices = slot.attachmentVertices
if #vertices < vertexCount then
if not vertices or #vertices < vertexCount then
vertices = {}
slot.attachmentVertices = vertices
end
@ -620,16 +620,15 @@ function Animation.FfdTimeline.new ()
alpha = 1 -- Don't mix from uninitialized slot vertices.
end
slot.attachmentVerticesCount = vertexCount
if time >= frames[#frames] then -- Time is after last frame.
local lastVertices = frameVertices[#frames]
if time >= frames[#frames - 1] then -- Time is after last frame.
local lastVertices = frameVertices[#frames - 1]
if alpha < 1 then
for i = 0, vertexCount do
for i = 1, vertexCount do
local vertex = vertices[i]
vertices[i] = vertex + (lastVertices[i] - vertex) * alpha
end
else
for i = 0, vertexCount do
for i = 1, vertexCount do
vertices[i] = lastVertices[i]
end
end
@ -647,13 +646,13 @@ function Animation.FfdTimeline.new ()
local nextVertices = frameVertices[frameIndex]
if alpha < 1 then
for i = 0, vertexCount do
for i = 1, vertexCount do
local prev = prevVertices[i]
local vertices = vertices[i]
vertices[i] = vertices + (prev + (nextVertices[i] - prev) * percent - vertices) * alpha
local vertex = vertices[i]
vertices[i] = vertex + (prev + (nextVertices[i] - prev) * percent - vertex) * alpha
end
else
for i = 0, vertexCount do
for i = 1, vertexCount do
local prev = prevVertices[i]
vertices[i] = prev + (nextVertices[i] - prev) * percent
end

View File

@ -32,6 +32,8 @@
local AttachmentType = require "spine-lua.AttachmentType"
local RegionAttachment = require "spine-lua.RegionAttachment"
local BoundingBoxAttachment = require "spine-lua.BoundingBoxAttachment"
local MeshAttachment = require "spine-lua.MeshAttachment"
local SkinningMeshAttachment = require "spine-lua.SkinnedMeshAttachment"
local AttachmentLoader = {}
function AttachmentLoader.new ()

View File

@ -34,7 +34,7 @@ local AttachmentType = require "spine-lua.AttachmentType"
local MeshAttachment = {}
function MeshAttachment.new (name)
if not name then error("name cannot be nil", 2) end
local self = {
name = name,
type = AttachmentType.mesh,
@ -46,7 +46,7 @@ function MeshAttachment.new (name)
r = 1, g = 1, b = 1, a = 1,
path = nil,
rendererObject = nil,
regionU = 0, regionV = 0, regionU2 = 0, regionV2 = 0, regionRotate = false,
regionU = 0, regionV = 0, regionU2 = 1, regionV2 = 1, regionRotate = false,
regionOffsetX = 0, regionOffsetY = 0,
regionWidth = 0, regionHeight = 0,
regionOriginalWidth = 0, regionOriginalHeight = 0,
@ -75,12 +75,13 @@ function MeshAttachment.new (name)
function self:computeWorldVertices (x, y, slot, worldVertices)
local bone = slot.bone
x,y=slot.bone.skeleton.x,slot.bone.skeleton.y
x = x + bone.worldX
y = y + bone.worldY
local m00, m01, m10, m11 = bone.m00, bone.m01, bone.m10, bone.m11
local vertices = self.vertices
local verticesCount = vertices.length
if #slot.attachmentVertices == verticesCount then vertices = slot.attachmentVertices end
local verticesCount = #vertices
if slot.attachmentVertices and #slot.attachmentVertices == verticesCount then vertices = slot.attachmentVertices end
for i = 1, verticesCount, 2 do
local vx = vertices[i]
local vy = vertices[i + 1]

View File

@ -178,9 +178,8 @@ function SkeletonJson.new (attachmentLoader)
end
if skin.name == "default" then
skeletonData.defaultSkin = skin
else
table.insert(skeletonData.skins, skin)
end
table.insert(skeletonData.skins, skin)
end
end
@ -266,19 +265,21 @@ function SkeletonJson.new (attachmentLoader)
return mesh
elseif type == AttachmentType.skinnedmesh then
local mesh = self.attachmentLoader.newSkinnedMeshAttachment(skin, name, path)
local mesh = self.attachmentLoader.newSkinningMeshAttachment(skin, name, path)
if not mesh then return null end
mesh.path = path
local uvs = getArray(map, "uvs", 1)
vertices = getArray(map, "vertices", 1)
local vertices = getArray(map, "vertices", 1)
local weights = {}
local bones = {}
for i = 1, vertices do
local i, n = 1, #vertices
while i < n do
local boneCount = vertices[i]
i = i + 1
table.insert(bones, boneCount)
for ii = 1, i + boneCount * 4 do
local nn = i + boneCount * 4
while i < nn do
table.insert(bones, vertices[i])
table.insert(weights, vertices[i + 1] * scale)
table.insert(weights, vertices[i + 2] * scale)
@ -468,22 +469,21 @@ function SkeletonJson.new (attachmentLoader)
local ffd = map["ffd"]
if ffd then
for skinName,slotMap in pairs(ffd) do
local skin = skeletonData.findSkin(skinName)
local skin = skeletonData:findSkin(skinName)
for slotName,meshMap in pairs(slotMap) do
local slotIndex = skeletonData.findSlotIndex(slotName)
local slotIndex = skeletonData:findSlotIndex(slotName)
for meshName,values in pairs(meshMap) do
local timeline = Animation.FfdTimeline.new()
local attachment = skin:getAttachment(slotIndex, meshName)
if not attachment then error("FFD attachment not found: " .. meshName) end
timeline.slotIndex = slotIndex
timeline.attachment = attachment
local isMesh = attachment.type == AttachmentType.mesh
local vertexCount
if isMesh then
vertexCount = attachment.vertices.length
vertexCount = #attachment.vertices
else
vertexCount = attachment.weights.length / 3 * 2
vertexCount = #attachment.weights / 3 * 2
end
local frameIndex = 0
@ -494,12 +494,18 @@ function SkeletonJson.new (attachmentLoader)
vertices = attachment.vertices
else
vertices = {}
vertices.length = vertexCount
for i = 1, vertexCount do
vertices[i] = 0
end
end
else
local verticesValue = valueMap["vertices"]
local vertices = {}
local scale = self.scale
vertices = {}
local start = valueMap["offset"] or 0
for ii = 1, start do
vertices[ii] = 0
end
if scale == 1 then
for ii = 1, #verticesValue do
vertices[ii + start] = verticesValue[ii]
@ -518,7 +524,6 @@ function SkeletonJson.new (attachmentLoader)
vertices[vertexCount] = 0
end
end
timeline:setFrame(frameIndex, valueMap["time"], vertices)
readCurve(timeline, frameIndex, valueMap)
frameIndex = frameIndex + 1

View File

@ -37,7 +37,7 @@ function SkinnedMeshAttachment.new (name)
local self = {
name = name,
type = AttachmentType.mesh,
type = AttachmentType.skinnedmesh,
bones = nil,
weights = nil,
uvs = nil,
@ -47,7 +47,7 @@ function SkinnedMeshAttachment.new (name)
r = 1, g = 1, b = 1, a = 1,
path = nil,
rendererObject = nil,
regionU = 0, regionV = 0, regionU2 = 0, regionV2 = 0, regionRotate = false,
regionU = 0, regionV = 0, regionU2 = 1, regionV2 = 1, regionRotate = false,
regionOffsetX = 0, regionOffsetY = 0,
regionWidth = 0, regionHeight = 0,
regionOriginalWidth = 0, regionOriginalHeight = 0,
@ -75,21 +75,21 @@ function SkinnedMeshAttachment.new (name)
end
function self:computeWorldVertices (x, y, slot, worldVertices)
local skeletonBones = slot.skeleton.bones
local skeletonBones = slot.bone.skeleton.bones
x,y=slot.bone.skeleton.x,slot.bone.skeleton.y
local weights = self.weights
local bones = self.bones
local w, v, b, f = 0, 0, 0, 0
local n = bones.length
local w, v, b, f = 1, 1, 1, 1
local n = #bones
local wx, wy, bone, vx, vy, weight
if #slot.attachmentVertices == 0 then
while v < n do
if slot.attachmentVerticesCount == 0 then
while v <= n do
wx = 0
wy = 0
local nn = bones[v] + v
v = v + 1
while v <= nn do
bone = skeletonBones[bones[v]]
bone = skeletonBones[bones[v] + 1]
vx = weights[b]
vy = weights[b + 1]
weight = weights[b + 2]
@ -104,13 +104,13 @@ function SkinnedMeshAttachment.new (name)
end
else
local ffd = slot.attachmentVertices
while v < n do
while v <= n do
wx = 0
wy = 0
local nn = bones[v] + v
v = v + 1
while v <= nn do
bone = skeletonBones[bones[v]]
bone = skeletonBones[bones[v] + 1]
vx = weights[b] + ffd[f]
vy = weights[b + 1] + ffd[f + 1]
weight = weights[b + 2]

View File

@ -41,7 +41,7 @@ public class SkeletonDataAsset : ScriptableObject {
public tk2dSpriteCollectionData spriteCollection;
#endif
public TextAsset skeletonJSON;
public float scale = 1;
public float scale = 0.01f;
public String[] fromAnimation;
public String[] toAnimation;
public float[] duration;

View File

@ -50,8 +50,8 @@ public class SkeletonAnimationInspector : SkeletonRendererInspector {
m_isPrefab = true;
}
protected override void gui () {
base.gui();
protected override void DrawInspectorGUI () {
base.DrawInspectorGUI();
SkeletonAnimation component = (SkeletonAnimation)target;
if (!component.valid)

View File

@ -21,8 +21,8 @@ public class SkeletonAnimatorInspector : SkeletonRendererInspector {
isPrefab = true;
}
protected override void gui () {
base.gui();
protected override void DrawInspectorGUI () {
base.DrawInspectorGUI();
EditorGUILayout.PropertyField(layerMixModes, true);

View File

@ -63,7 +63,7 @@ public class SkeletonRendererInspector : Editor {
sortingLayerIDProperty = rendererSerializedObject.FindProperty("m_SortingLayerID");
}
protected virtual void gui () {
protected virtual void DrawInspectorGUI () {
SkeletonRenderer component = (SkeletonRenderer)target;
EditorGUILayout.BeginHorizontal();
EditorGUILayout.PropertyField(skeletonDataAsset);
@ -134,7 +134,7 @@ public class SkeletonRendererInspector : Editor {
EditorGUILayout.PropertyField(submeshSeparators, true);
EditorGUILayout.Space();
EditorGUILayout.PropertyField(meshes,
new GUIContent("Render Meshes", "Disable to optimize rendering for skeletons that don't use meshes"));
new GUIContent("Render Mesh Attachments", "Disable to optimize rendering for skeletons that don't use Mesh Attachments"));
EditorGUILayout.PropertyField(immutableTriangles,
new GUIContent("Immutable Triangles", "Enable to optimize rendering for skeletons that never change attachment visbility"));
EditorGUILayout.Space();
@ -154,7 +154,7 @@ public class SkeletonRendererInspector : Editor {
override public void OnInspectorGUI () {
serializedObject.Update();
gui();
DrawInspectorGUI();
if (serializedObject.ApplyModifiedProperties() ||
(Event.current.type == EventType.ValidateCommand && Event.current.commandName == "UndoRedoPerformed")
) {

View File

@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 93a799664eb27fd4183aed06719b306c
folderAsset: yes
timeCreated: 1455486322
licenseType: Free
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,500 @@
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using Spine;
using Spine.Unity;
public class ArraysSubmeshedMeshGenerator : Spine.Unity.ISubmeshedMeshGenerator {
readonly List<Slot> separators = new List<Slot>();
public List<Slot> Separators { get { return this.separators; } }
public bool generateNormals;
public bool generateTangents;
public float zSpacing = 0f;
public SubmeshedMeshInstructions GenerateInstructions (Skeleton skeleton) {
if (skeleton == null) throw new System.ArgumentNullException("skeleton");
// Count vertices and submesh triangles.
int runningVertexCount = 0;
int submeshTriangleCount = 0;
int submeshFirstVertex = 0;
int submeshVertexCount = 0;
int submeshStartSlotIndex = 0;
Material lastMaterial = null;
var drawOrder = skeleton.drawOrder;
var drawOrderItems = drawOrder.Items;
int drawOrderCount = drawOrder.Count;
int separatorCount = separators.Count;
var instructionList = this.currentInstructions.submeshInstructions;
instructionList.Clear();
currentInstructions.attachmentList.Clear();
for (int i = 0; i < drawOrderCount; i++) {
var slot = drawOrderItems[i];
var attachment = slot.attachment;
object rendererObject; // An AtlasRegion in plain Spine-Unity. eventual source of Material object.
int attachmentVertexCount, attachmentTriangleCount;
var regionAttachment = attachment as RegionAttachment;
if (regionAttachment != null) {
rendererObject = regionAttachment.RendererObject;
attachmentVertexCount = 4;
attachmentTriangleCount = 6;
} else {
var meshAttachment = attachment as MeshAttachment;
if (meshAttachment != null) {
rendererObject = meshAttachment.RendererObject;
attachmentVertexCount = meshAttachment.vertices.Length >> 1;
attachmentTriangleCount = meshAttachment.triangles.Length;
} else {
var skinnedMeshAttachment = attachment as WeightedMeshAttachment;
if (skinnedMeshAttachment != null) {
rendererObject = skinnedMeshAttachment.RendererObject;
attachmentVertexCount = skinnedMeshAttachment.uvs.Length >> 1;
attachmentTriangleCount = skinnedMeshAttachment.triangles.Length;
} else
continue;
}
}
var attachmentMaterial = (Material)((AtlasRegion)rendererObject).page.rendererObject;
// Populate submesh when material changes. (or when forced to separate by a submeshSeparator)
if (( runningVertexCount > 0 && lastMaterial.GetInstanceID() != attachmentMaterial.GetInstanceID() ) ||
//if (( lastMaterial != null && lastMaterial.GetInstanceID() != attachmentMaterial.GetInstanceID() ) ||
( separatorCount > 0 && separators.Contains(slot) )) {
instructionList.Add(
new SubmeshInstructions {
skeleton = skeleton,
material = lastMaterial,
triangleCount = submeshTriangleCount,
vertexCount = submeshVertexCount,
startSlot = submeshStartSlotIndex,
endSlot = i,
firstVertexIndex = submeshFirstVertex
}
);
// Prepare for next submesh
submeshTriangleCount = 0;
submeshVertexCount = 0;
submeshFirstVertex = runningVertexCount;
submeshStartSlotIndex = i;
}
lastMaterial = attachmentMaterial;
submeshTriangleCount += attachmentTriangleCount;
submeshVertexCount += attachmentVertexCount;
runningVertexCount += attachmentVertexCount;
currentInstructions.attachmentList.Add(attachment);
}
instructionList.Add(
new SubmeshInstructions {
skeleton = skeleton,
material = lastMaterial,
triangleCount = submeshTriangleCount,
vertexCount = submeshVertexCount,
startSlot = submeshStartSlotIndex,
endSlot = drawOrderCount,
firstVertexIndex = submeshFirstVertex
}
);
currentInstructions.vertexCount = runningVertexCount;
return currentInstructions;
}
public SubmeshedMesh GenerateMesh (SubmeshedMeshInstructions meshInstructions) {
var smartMesh = doubleBufferedSmartMesh.GetNextMesh();
var mesh = smartMesh.mesh;
int submeshCount = meshInstructions.submeshInstructions.Count;
var instructionList = meshInstructions.submeshInstructions;
float zSpacing = this.zSpacing;
float[] attVertBuffer = this.attachmentVertexBuffer;
Vector2[] uvs = this.meshUVs;
Color32[] colors32 = this.meshColors32;
Color32 color;
// Ensure correct buffer sizes.
Vector3[] vertices = this.meshVertices;
bool newVertices = vertices == null || meshInstructions.vertexCount > vertices.Length;
int instructionVertexCount = meshInstructions.vertexCount;
if (newVertices) {
this.meshVertices = vertices = new Vector3[instructionVertexCount];
this.meshColors32 = colors32 = new Color32[instructionVertexCount];
this.meshUVs = uvs = new Vector2[instructionVertexCount];
} else {
var zero = Vector3.zero;
for (int i = instructionVertexCount, n = this.meshVertices.Length; i < n; i++)
vertices[i] = zero;
}
bool newSubmeshBuffers = submeshBuffers.Count < submeshCount;
if (newSubmeshBuffers) {
submeshBuffers.GrowIfNeeded(submeshCount);
for (int i = submeshBuffers.Count; submeshBuffers.Count < submeshCount; i++) {
submeshBuffers.Add(new SubmeshTriangleBuffer(instructionList.Items[i].triangleCount));
//submeshBuffers.Items[i] = new SubmeshTriangleBuffer(tc);
//submeshBuffers.Count = i;
}
}
Vector3 meshBoundsMin;
Vector3 meshBoundsMax;
int attachmentCount = meshInstructions.attachmentList.Count;
// Initial values for manual Mesh Bounds calculation
if (meshInstructions.attachmentList.Count <= 0) {
meshBoundsMin = new Vector3(0, 0, 0);
meshBoundsMax = new Vector3(0, 0, 0);
} else {
meshBoundsMin.x = int.MaxValue;
meshBoundsMin.y = int.MaxValue;
meshBoundsMax.x = int.MinValue;
meshBoundsMax.y = int.MinValue;
if (zSpacing > 0f) {
meshBoundsMin.z = 0f;
meshBoundsMax.z = zSpacing * (attachmentCount - 1);
} else {
meshBoundsMin.z = zSpacing * (attachmentCount - 1);
meshBoundsMax.z = 0f;
}
}
bool structureDoesntMatch = newVertices || newSubmeshBuffers || smartMesh.StructureDoesntMatch(meshInstructions);
if (structureDoesntMatch) {
mesh.Clear();
if (submeshCount == sharedMaterials.Length)
meshInstructions.FillMaterialArray(this.sharedMaterials);
else
this.sharedMaterials = meshInstructions.GetNewMaterialArray();
}
int vertexIndex = 0;
// For each submesh, add vertex data from attachments.
for (int submeshIndex = 0; submeshIndex < submeshCount; submeshIndex++) {
var currentSubmeshInstruction = instructionList.Items[submeshIndex];
var skeleton = currentSubmeshInstruction.skeleton;
var skeletonDrawOrderItems = skeleton.DrawOrder.Items;
float a = skeleton.a * 255, r = skeleton.r, g = skeleton.g, b = skeleton.b;
for (int slotIndex = currentSubmeshInstruction.startSlot, endSlot = currentSubmeshInstruction.endSlot; slotIndex < endSlot; slotIndex++) {
var slot = skeletonDrawOrderItems[slotIndex];
var attachment = slot.attachment;
float z = slotIndex * zSpacing;
var regionAttachment = attachment as RegionAttachment;
if (regionAttachment != null) {
regionAttachment.ComputeWorldVertices(slot.bone, attVertBuffer);
float x1 = attVertBuffer[RegionAttachment.X1], y1 = attVertBuffer[RegionAttachment.Y1];
float x2 = attVertBuffer[RegionAttachment.X2], y2 = attVertBuffer[RegionAttachment.Y2];
float x3 = attVertBuffer[RegionAttachment.X3], y3 = attVertBuffer[RegionAttachment.Y3];
float x4 = attVertBuffer[RegionAttachment.X4], y4 = attVertBuffer[RegionAttachment.Y4];
vertices[vertexIndex].x = x1; vertices[vertexIndex].y = y1; vertices[vertexIndex].z = z;
vertices[vertexIndex + 1].x = x4; vertices[vertexIndex + 1].y = y4; vertices[vertexIndex + 1].z = z;
vertices[vertexIndex + 2].x = x2; vertices[vertexIndex + 2].y = y2; vertices[vertexIndex + 2].z = z;
vertices[vertexIndex + 3].x = x3; vertices[vertexIndex + 3].y = y3; vertices[vertexIndex + 3].z = z;
color.a = (byte)(a * slot.a * regionAttachment.a);
color.r = (byte)(r * slot.r * regionAttachment.r * color.a);
color.g = (byte)(g * slot.g * regionAttachment.g * color.a);
color.b = (byte)(b * slot.b * regionAttachment.b * color.a);
if (slot.data.blendMode == BlendMode.additive) color.a = 0;
colors32[vertexIndex] = color; colors32[vertexIndex + 1] = color; colors32[vertexIndex + 2] = color; colors32[vertexIndex + 3] = color;
float[] regionUVs = regionAttachment.uvs;
uvs[vertexIndex].x = regionUVs[RegionAttachment.X1]; uvs[vertexIndex].y = regionUVs[RegionAttachment.Y1];
uvs[vertexIndex + 1].x = regionUVs[RegionAttachment.X4]; uvs[vertexIndex + 1].y = regionUVs[RegionAttachment.Y4];
uvs[vertexIndex + 2].x = regionUVs[RegionAttachment.X2]; uvs[vertexIndex + 2].y = regionUVs[RegionAttachment.Y2];
uvs[vertexIndex + 3].x = regionUVs[RegionAttachment.X3]; uvs[vertexIndex + 3].y = regionUVs[RegionAttachment.Y3];
// Calculate min/max X
if (x1 < meshBoundsMin.x) meshBoundsMin.x = x1;
else if (x1 > meshBoundsMax.x) meshBoundsMax.x = x1;
if (x2 < meshBoundsMin.x) meshBoundsMin.x = x2;
else if (x2 > meshBoundsMax.x) meshBoundsMax.x = x2;
if (x3 < meshBoundsMin.x) meshBoundsMin.x = x3;
else if (x3 > meshBoundsMax.x) meshBoundsMax.x = x3;
if (x4 < meshBoundsMin.x) meshBoundsMin.x = x4;
else if (x4 > meshBoundsMax.x) meshBoundsMax.x = x4;
// Calculate min/max Y
if (y1 < meshBoundsMin.y) meshBoundsMin.y = y1;
else if (y1 > meshBoundsMax.y) meshBoundsMax.y = y1;
if (y2 < meshBoundsMin.y) meshBoundsMin.y = y2;
else if (y2 > meshBoundsMax.y) meshBoundsMax.y = y2;
if (y3 < meshBoundsMin.y) meshBoundsMin.y = y3;
else if (y3 > meshBoundsMax.y) meshBoundsMax.y = y3;
if (y4 < meshBoundsMin.y) meshBoundsMin.y = y4;
else if (y4 > meshBoundsMax.y) meshBoundsMax.y = y4;
vertexIndex += 4;
} else {
var meshAttachment = attachment as MeshAttachment;
if (meshAttachment != null) {
int meshVertexCount = meshAttachment.vertices.Length;
if (attVertBuffer.Length < meshVertexCount) this.attachmentVertexBuffer = attVertBuffer = new float[meshVertexCount];
meshAttachment.ComputeWorldVertices(slot, attVertBuffer);
color.a = (byte)(a * slot.a * meshAttachment.a);
color.r = (byte)(r * slot.r * meshAttachment.r * color.a);
color.g = (byte)(g * slot.g * meshAttachment.g * color.a);
color.b = (byte)(b * slot.b * meshAttachment.b * color.a);
if (slot.data.blendMode == BlendMode.additive) color.a = 0;
float[] attachmentUVs = meshAttachment.uvs;
for (int iii = 0; iii < meshVertexCount; iii += 2) {
float x = attVertBuffer[iii], y = attVertBuffer[iii + 1];
vertices[vertexIndex].x = x; vertices[vertexIndex].y = y; vertices[vertexIndex].z = z;
colors32[vertexIndex] = color; uvs[vertexIndex].x = attachmentUVs[iii]; uvs[vertexIndex].y = attachmentUVs[iii + 1];
if (x < meshBoundsMin.x) meshBoundsMin.x = x;
else if (x > meshBoundsMax.x) meshBoundsMax.x = x;
if (y < meshBoundsMin.y) meshBoundsMin.y = y;
else if (y > meshBoundsMax.y) meshBoundsMax.y = y;
vertexIndex++;
}
} else {
var weightedMeshAttachment = attachment as WeightedMeshAttachment;
if (weightedMeshAttachment != null) {
int meshVertexCount = weightedMeshAttachment.uvs.Length;
if (attVertBuffer.Length < meshVertexCount) this.attachmentVertexBuffer = attVertBuffer = new float[meshVertexCount];
weightedMeshAttachment.ComputeWorldVertices(slot, attVertBuffer);
color.a = (byte)(a * slot.a * weightedMeshAttachment.a);
color.r = (byte)(r * slot.r * weightedMeshAttachment.r * color.a);
color.g = (byte)(g * slot.g * weightedMeshAttachment.g * color.a);
color.b = (byte)(b * slot.b * weightedMeshAttachment.b * color.a);
if (slot.data.blendMode == BlendMode.additive) color.a = 0;
float[] attachmentUVs = weightedMeshAttachment.uvs;
for (int iii = 0; iii < meshVertexCount; iii += 2) {
float x = attVertBuffer[iii], y = attVertBuffer[iii + 1];
vertices[vertexIndex].x = x; vertices[vertexIndex].y = y; vertices[vertexIndex].z = z;
colors32[vertexIndex] = color;
uvs[vertexIndex].x = attachmentUVs[iii]; uvs[vertexIndex].y = attachmentUVs[iii + 1];
if (x < meshBoundsMin.x) meshBoundsMin.x = x;
else if (x > meshBoundsMax.x) meshBoundsMax.x = x;
if (y < meshBoundsMin.y) meshBoundsMin.y = y;
else if (y > meshBoundsMax.y) meshBoundsMax.y = y;
vertexIndex++;
}
}
}
}
}
// Push triangles in this submesh
if (structureDoesntMatch) {
smartMesh.mesh.Clear(); // rebuild triangle array.
var currentSubmesh = submeshBuffers.Items[submeshIndex];
bool isLastSubmesh = (submeshIndex == submeshCount - 1);
int triangleCount = currentSubmesh.triangleCount = currentSubmeshInstruction.triangleCount;
int trianglesCapacity = currentSubmesh.triangles.Length;
int[] triangles = currentSubmesh.triangles;
if (isLastSubmesh) {
if (trianglesCapacity > triangleCount) {
for (int i = triangleCount; i < trianglesCapacity; i++)
triangles[i] = 0;
}
} else if (trianglesCapacity != triangleCount) {
triangles = currentSubmesh.triangles = new int[triangleCount];
currentSubmesh.triangleCount = 0;
}
// Iterate through submesh slots and store the triangles.
int triangleIndex = 0;
int afv = currentSubmeshInstruction.firstVertexIndex; // attachment first vertex
for (int i = currentSubmeshInstruction.startSlot, n = currentSubmeshInstruction.endSlot; i < n; i++) {
var attachment = skeletonDrawOrderItems[i].attachment;
if (attachment is RegionAttachment) {
triangles[triangleIndex] = afv; triangles[triangleIndex + 1] = afv + 2; triangles[triangleIndex + 2] = afv + 1;
triangles[triangleIndex + 3] = afv + 2; triangles[triangleIndex + 4] = afv + 3; triangles[triangleIndex + 5] = afv + 1;
triangleIndex += 6;
afv += 4;
} else {
int[] attachmentTriangles;
int attachmentVertexCount;
var meshAttachment = attachment as MeshAttachment;
if (meshAttachment != null) {
attachmentVertexCount = meshAttachment.vertices.Length >> 1; // length/2
attachmentTriangles = meshAttachment.triangles;
} else {
var weightedMeshAttachment = attachment as WeightedMeshAttachment;
if (weightedMeshAttachment != null) {
attachmentVertexCount = weightedMeshAttachment.uvs.Length >> 1; // length/2
attachmentTriangles = weightedMeshAttachment.triangles;
} else
continue;
}
for (int ii = 0, nn = attachmentTriangles.Length; ii < nn; ii++, triangleIndex++)
triangles[triangleIndex] = afv + attachmentTriangles[ii];
afv += attachmentVertexCount;
}
} // Done adding current submesh triangles
}
}
Vector3 meshBoundsExtents = (meshBoundsMax - meshBoundsMin);
Vector3 meshCenter = meshBoundsMin + meshBoundsExtents * 0.5f;
smartMesh.Set(this.meshVertices, this.meshUVs, this.meshColors32, meshInstructions);
mesh.bounds = new Bounds(meshCenter, meshBoundsExtents);
if (structureDoesntMatch) {
if (generateNormals) {
int vertexCount = meshInstructions.vertexCount;
Vector3[] normals = new Vector3[vertexCount];
Vector3 normal = new Vector3(0, 0, -1);
for (int i = 0; i < vertexCount; i++)
normals[i] = normal;
mesh.normals = normals;
if (generateTangents) {
Vector4[] tangents = new Vector4[vertexCount];
Vector4 tangent = new Vector4(1, 0, 0, -1);
for (int i = 0; i < vertexCount; i++)
tangents[i] = tangent;
mesh.tangents = tangents;
}
}
// push new triangles if doesn't match.
mesh.subMeshCount = submeshCount;
for (int i = 0; i < submeshCount; i++)
mesh.SetTriangles(submeshBuffers.Items[i].triangles, i);
}
return new SubmeshedMesh(smartMesh.mesh, sharedMaterials);
}
#region Internals
readonly DoubleBufferedSmartMesh doubleBufferedSmartMesh = new DoubleBufferedSmartMesh();
readonly SubmeshedMeshInstructions currentInstructions = new SubmeshedMeshInstructions();
float[] attachmentVertexBuffer = new float[8];
Vector3[] meshVertices;
Color32[] meshColors32;
Vector2[] meshUVs;
Material[] sharedMaterials = new Material[0];
readonly ExposedList<SubmeshTriangleBuffer> submeshBuffers = new ExposedList<SubmeshTriangleBuffer>();
#endregion
#region Types
class SubmeshTriangleBuffer {
public int[] triangles;
public int triangleCount;
public SubmeshTriangleBuffer (int triangleCount) {
triangles = new int[triangleCount];
this.triangleCount = triangleCount;
}
}
class DoubleBufferedSmartMesh {
readonly SmartMesh mesh1 = new SmartMesh();
readonly SmartMesh mesh2 = new SmartMesh();
bool usingMesh1;
public SmartMesh GetNextMesh () {
usingMesh1 = !usingMesh1;
return usingMesh1 ? mesh1 : mesh2;
}
}
// A SmartMesh is a Mesh (with submeshes) that knows what attachments and instructions were used to generate it.
class SmartMesh {
public readonly Mesh mesh = SpineMesh.NewMesh();
readonly ExposedList<Attachment> attachmentsUsed = new ExposedList<Attachment>();
readonly ExposedList<SubmeshInstructions> instructionsUsed = new ExposedList<SubmeshInstructions>();
public void Set (Vector3[] verts, Vector2[] uvs, Color32[] colors, SubmeshedMeshInstructions instructions) {
mesh.vertices = verts;
mesh.uv = uvs;
mesh.colors32 = colors;
attachmentsUsed.Clear();
attachmentsUsed.GrowIfNeeded(instructions.attachmentList.Capacity);
attachmentsUsed.Count = instructions.attachmentList.Count;
instructions.attachmentList.CopyTo(attachmentsUsed.Items);
instructionsUsed.Clear();
instructionsUsed.GrowIfNeeded(instructions.submeshInstructions.Capacity);
instructionsUsed.Count = instructions.submeshInstructions.Count;
instructions.submeshInstructions.CopyTo(instructionsUsed.Items);
}
public bool StructureDoesntMatch (SubmeshedMeshInstructions instructions) {
// Check count inequality.
if (instructions.attachmentList.Count != this.attachmentsUsed.Count) return true;
if (instructions.submeshInstructions.Count != this.instructionsUsed.Count) return true;
//Debug.Log("broadphase matched");
// Check each attachment.
var attachmentsPassed = instructions.attachmentList.Items;
var myAttachments = this.attachmentsUsed.Items;
for (int i = 0, n = attachmentsUsed.Count; i < n; i++)
if (attachmentsPassed[i] != myAttachments[i]) return true;
//Debug.Log("attachments matched");
// Check each submesh for equal arrangement.
var instructionListItems = instructions.submeshInstructions.Items;
var myInstructions = this.instructionsUsed.Items;
for (int i = 0, n = this.instructionsUsed.Count; i < n; i++) {
var lhs = instructionListItems[i];
var rhs = myInstructions[i];
if (
lhs.skeleton != rhs.skeleton ||
lhs.material.GetInstanceID() != rhs.material.GetInstanceID() ||
lhs.startSlot != rhs.startSlot ||
lhs.endSlot != rhs.endSlot ||
lhs.triangleCount != rhs.triangleCount ||
lhs.vertexCount != rhs.vertexCount ||
lhs.firstVertexIndex != rhs.firstVertexIndex
) return true;
}
//Debug.Log("structure matched");
return false;
}
}
#endregion
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 412898b02d5caaf489b3ffb29e4ae1c0
timeCreated: 1455407003
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,69 @@
using UnityEngine;
using System.Collections.Generic;
namespace Spine.Unity {
public interface ISubmeshedMeshGenerator {
/// <summary>Generates instructions for how to generate the submeshed mesh based on the given state of the
/// skeleton. The returned instructions can be used to generate a whole submeshed mesh or individual submeshes.</summary>
SubmeshedMeshInstructions GenerateInstructions (Skeleton skeleton);
/// <summary>Returns a SubmeshedMesh (a mesh and a material array coupled in a struct).
/// Call GenerateInstructions to get the SubmeshedMeshInstructions to pass into this.</summary>
SubmeshedMesh GenerateMesh (SubmeshedMeshInstructions wholeMeshInstruction);
/// <summary>A list of slots that mark the end of a submesh. The slot after it will be the start of a new submesh.</summary>
List<Slot> Separators { get; }
}
public interface ISingleSubmeshGenerator {
void FillMesh (SubmeshInstructions instructions, Mesh meshToFill);
}
/// <summary>A Submeshed mesh is a return type so the mesh with
/// multiple submeshes can be coupled with a material array to render its submeshes.</summary>
public struct SubmeshedMesh {
public readonly Mesh mesh;
public readonly Material[] materials;
public SubmeshedMesh (Mesh mesh, Material[] materials) {
this.mesh = mesh;
this.materials = materials;
}
}
/// <summary>Primarily a collection of Submesh Instructions. This constitutes instructions for how to construct a mesh containing submeshes.</summary>
public class SubmeshedMeshInstructions {
public readonly ExposedList<SubmeshInstructions> submeshInstructions = new ExposedList<SubmeshInstructions>();
public readonly ExposedList<Attachment> attachmentList = new ExposedList<Attachment>();
public int vertexCount = -1;
/// <summary>Allocates a new material array to render this mesh and its constituent submeshes.</summary>
public Material[] GetNewMaterialArray () {
var materials = new Material[submeshInstructions.Count];
FillMaterialArray(materials);
return materials;
}
/// <summary>Fills a given array with the materials needed to render this submeshed mesh.</summary>
public void FillMaterialArray (Material[] materialArray) {
var instructionsItems = submeshInstructions.Items;
for (int i = 0, n = materialArray.Length; i < n; i++)
materialArray[i] = instructionsItems[i].material;
}
}
/// <summary>Instructions for how to generate a mesh or submesh out of a range of slots in a given skeleton.</summary>
public struct SubmeshInstructions {
public Skeleton skeleton;
public int startSlot;
public int endSlot;
// Cached values because they are determined in the process of generating instructions,
// but could otherwise be pulled from accessing attachments, checking materials and counting tris and verts.
public Material material;
public int triangleCount;
public int vertexCount;
// Vertex index offset. Used by submesh generation if part of a bigger mesh.
public int firstVertexIndex;
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: c518552ea622e98418ce673c5febc468
timeCreated: 1455406760
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -13,7 +13,7 @@ public class SkeletonGraphicInspector : Editor {
SerializedProperty material_, color_;
SerializedProperty skeletonDataAsset_, initialSkinName_;
SerializedProperty startingAnimation_, startingLoop_, timeScale_, freeze_;
#if !PREUNITY_5_2
#if !PREUNITY_5_2
SerializedProperty raycastTarget_;
SkeletonGraphic thisSkeletonGraphic;
@ -38,17 +38,12 @@ public class SkeletonGraphicInspector : Editor {
freeze_ = so.FindProperty("freeze");
}
public override void OnInspectorGUI () {
var s = thisSkeletonGraphic;
s.skeletonDataAsset = SkeletonGraphicInspector.ObjectField<SkeletonDataAsset>(skeletonDataAsset_);
s.material = SkeletonGraphicInspector.ObjectField<Material>(material_);
EditorGUI.BeginChangeCheck();
thisSkeletonGraphic.color = EditorGUILayout.ColorField(color_.displayName, color_.colorValue);
if (EditorGUI.EndChangeCheck())
SkeletonGraphicInspector.ForceUpdateHack(thisSkeletonGraphic.transform);
EditorGUILayout.PropertyField(skeletonDataAsset_);
EditorGUILayout.PropertyField(material_);
EditorGUILayout.PropertyField(color_);
if (thisSkeletonGraphic.skeletonDataAsset == null) {
EditorGUILayout.HelpBox("You need to assign a SkeletonDataAsset first.", MessageType.Info);
@ -62,49 +57,34 @@ public class SkeletonGraphicInspector : Editor {
EditorGUILayout.Space();
EditorGUILayout.LabelField("Animation", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(startingAnimation_);
s.startingLoop = SkeletonGraphicInspector.BoolField(startingLoop_);
s.timeScale = EditorGUILayout.FloatField(timeScale_.displayName, timeScale_.floatValue);
EditorGUILayout.PropertyField(startingLoop_);
EditorGUILayout.PropertyField(timeScale_);
EditorGUILayout.Space();
s.freeze = SkeletonGraphicInspector.BoolField(freeze_);
EditorGUILayout.PropertyField(freeze_);
EditorGUILayout.Space();
EditorGUILayout.LabelField("UI", EditorStyles.boldLabel);
s.raycastTarget = SkeletonGraphicInspector.BoolField(raycastTarget_);
EditorGUILayout.PropertyField(raycastTarget_);
}
bool wasChanged = EditorGUI.EndChangeCheck();
#region HAX - Thanks, Unity
// colors weren't updating in realtime in the custom inspector.
// Why the hell do I have to do this??
/// <summary>Use this when scene repaint and proper explicit update methods don't work.</summary>
public static void ForceUpdateHack (Transform t) {
var origValue = t.localScale;
t.localScale = new Vector3(11f, 22f, 33f);
t.localScale = origValue;
if (wasChanged) {
serializedObject.ApplyModifiedProperties();
}
}
// Hack for Unity 5.3 problem with PropertyField
public static T ObjectField<T> (SerializedProperty property) where T : UnityEngine.Object {
return (T)EditorGUILayout.ObjectField(property.displayName, property.objectReferenceValue, typeof(T), false);
}
public static bool BoolField (SerializedProperty property) {
return EditorGUILayout.Toggle(property.displayName, property.boolValue);
}
#endregion
#region Menus
[MenuItem ("CONTEXT/SkeletonGraphic/Match RectTransform with Mesh Bounds")]
[MenuItem("CONTEXT/SkeletonGraphic/Match RectTransform with Mesh Bounds")]
static void MatchRectTransformWithBounds (MenuCommand command) {
var skeletonGraphic = (SkeletonGraphic)command.context;
var mesh = skeletonGraphic.SpineMeshGenerator.LastGeneratedMesh;
var mesh = skeletonGraphic.SpineMeshGenerator.LastGeneratedMesh;
var bounds = mesh.bounds;
var size = bounds.size;
var center = bounds.center;
var p = new Vector2(
0.5f - (center.x / size.x),
0.5f - (center.y / size.y)
);
0.5f - (center.x / size.x),
0.5f - (center.y / size.y)
);
skeletonGraphic.rectTransform.sizeDelta = size;
skeletonGraphic.rectTransform.pivot = p;
@ -112,8 +92,12 @@ public class SkeletonGraphicInspector : Editor {
public static Material DefaultSkeletonGraphicMaterial {
get {
var guids = AssetDatabase.FindAssets("SkeletonGraphicDefault t:material"); if (guids.Length <= 0) return null;
var firstAssetPath = AssetDatabase.GUIDToAssetPath(guids[0]); if (string.IsNullOrEmpty(firstAssetPath)) return null;
var guids = AssetDatabase.FindAssets("SkeletonGraphicDefault t:material");
if (guids.Length <= 0)
return null;
var firstAssetPath = AssetDatabase.GUIDToAssetPath(guids[0]);
if (string.IsNullOrEmpty(firstAssetPath))
return null;
var firstMaterial = AssetDatabase.LoadAssetAtPath<Material>(firstAssetPath);
return firstMaterial;
}
@ -204,6 +188,8 @@ public class SkeletonGraphicInspector : Editor {
graphic.material = SkeletonGraphicInspector.DefaultSkeletonGraphicMaterial;
return go;
}
#endregion
#endif
#endif
}

View File

@ -89,6 +89,10 @@ public class SkeletonGraphic : MaskableGraphic {
protected override void Reset () {
base.Reset();
if (canvas == null) {
Debug.LogWarningFormat("SkeletonGraphic requires a Canvas to be visible. Move this GameObject ({0}) in the Hierarchy so it becomes a child of a Canvas.", gameObject.name);
}
if (material == null || material.shader != Shader.Find("Spine/SkeletonGraphic (Premultiply Alpha)")) {
Debug.LogWarning("SkeletonGraphic works best with the SkeletonGraphic material.");
}

View File

@ -54,6 +54,10 @@ public class SkeletonRenderer : MonoBehaviour {
// Submesh Separation
[SpineSlot] public string[] submeshSeparators = new string[0];
[HideInInspector] public List<Slot> submeshSeparatorSlots = new List<Slot>();
// Custom Slot Material
[System.NonSerialized] private readonly Dictionary<Slot, Material> customSlotMaterials = new Dictionary<Slot, Material>();
public Dictionary<Slot, Material> CustomSlotMaterials { get { return customSlotMaterials; } }
#endregion
[System.NonSerialized] public bool valid;
@ -207,6 +211,8 @@ public class SkeletonRenderer : MonoBehaviour {
drawOrder.Count != storedAttachments.Count || // Number of slots changed (when does this happen?)
immutableTriangles != storedState.immutableTriangles; // Immutable Triangles flag changed.
bool isCustomMaterialsPopulated = customSlotMaterials.Count > 0;
for (int i = 0; i < drawOrderCount; i++) {
Slot slot = drawOrderItems[i];
Bone bone = slot.bone;
@ -252,13 +258,22 @@ public class SkeletonRenderer : MonoBehaviour {
}
#if !SPINE_TK2D
Material material = (Material)((AtlasRegion)rendererObject).page.rendererObject;
// Material material = (Material)((AtlasRegion)rendererObject).page.rendererObject; // For no customSlotMaterials
Material material;
if (isCustomMaterialsPopulated) {
if (!customSlotMaterials.TryGetValue(slot, out material)) {
material = (Material)((AtlasRegion)rendererObject).page.rendererObject;
}
} else {
material = (Material)((AtlasRegion)rendererObject).page.rendererObject;
}
#else
Material material = (rendererObject.GetType() == typeof(Material)) ? (Material)rendererObject : (Material)((AtlasRegion)rendererObject).page.rendererObject;
#endif
// Populate submesh when material changes. (or when forced to separate by a submeshSeparator)
if ((lastMaterial != null && lastMaterial.GetInstanceID() != material.GetInstanceID()) ||
if ((vertexCount > 0 && lastMaterial.GetInstanceID() != material.GetInstanceID()) ||
(submeshSeparatorSlotsCount > 0 && submeshSeparatorSlots.Contains(slot))) {
workingSubmeshArguments.Add(