mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-03-26 22:49:01 +08:00
Merge branch '3.8' into 4.0-beta
This commit is contained in:
commit
bf4df3b5f3
@ -375,6 +375,7 @@
|
|||||||
* Now providing `BeforeApply` update callbacks at all skeleton animation components (`SkeletonAnimation`, `SkeletonMecanim` and `SkeletonGraphic`).
|
* Now providing `BeforeApply` update callbacks at all skeleton animation components (`SkeletonAnimation`, `SkeletonMecanim` and `SkeletonGraphic`).
|
||||||
* Added `BoundingBoxFollowerGraphic` component. This class is a counterpart of `BoundingBoxFollower` that can be used with `SkeletonGraphic`.
|
* Added `BoundingBoxFollowerGraphic` component. This class is a counterpart of `BoundingBoxFollower` that can be used with `SkeletonGraphic`.
|
||||||
* Added Inspector context menu functions `SkeletonRenderer - Add all BoundingBoxFollower GameObjects` and `SkeletonGraphic - Add all BoundingBoxFollowerGraphic GameObjects` that automatically generate bounding box follower GameObjects for every `BoundingBoxAttachment` for all skins of a skeleton.
|
* Added Inspector context menu functions `SkeletonRenderer - Add all BoundingBoxFollower GameObjects` and `SkeletonGraphic - Add all BoundingBoxFollowerGraphic GameObjects` that automatically generate bounding box follower GameObjects for every `BoundingBoxAttachment` for all skins of a skeleton.
|
||||||
|
* `GetRemappedClone()` now provides an additional parameter `pivotShiftsMeshUVCoords` for `MeshAttachment` to prevent uv shifts at a non-central Sprite pivot. This parameter defaults to `true` to maintain previous behaviour.
|
||||||
|
|
||||||
* **Changes of default values**
|
* **Changes of default values**
|
||||||
* `SkeletonMecanim`'s `Layer Mix Mode` now defaults to `MixMode.MixNext` instead of `MixMode.MixAlways`.
|
* `SkeletonMecanim`'s `Layer Mix Mode` now defaults to `MixMode.MixNext` instead of `MixMode.MixAlways`.
|
||||||
|
|||||||
@ -94,13 +94,13 @@ namespace Spine.Unity.Examples {
|
|||||||
// Let's do this for the visor.
|
// Let's do this for the visor.
|
||||||
int visorSlotIndex = skeleton.FindSlotIndex(visorSlot); // You can access GetAttachment and SetAttachment via string, but caching the slotIndex is faster.
|
int visorSlotIndex = skeleton.FindSlotIndex(visorSlot); // You can access GetAttachment and SetAttachment via string, but caching the slotIndex is faster.
|
||||||
Attachment templateAttachment = templateSkin.GetAttachment(visorSlotIndex, visorKey); // STEP 1.1
|
Attachment templateAttachment = templateSkin.GetAttachment(visorSlotIndex, visorKey); // STEP 1.1
|
||||||
Attachment newAttachment = templateAttachment.GetRemappedClone(visorSprite, sourceMaterial); // STEP 1.2 - 1.3
|
Attachment newAttachment = templateAttachment.GetRemappedClone(visorSprite, sourceMaterial, pivotShiftsMeshUVCoords : false); // STEP 1.2 - 1.3
|
||||||
customSkin.SetAttachment(visorSlotIndex, visorKey, newAttachment); // STEP 1.4
|
customSkin.SetAttachment(visorSlotIndex, visorKey, newAttachment); // STEP 1.4
|
||||||
|
|
||||||
// And now for the gun.
|
// And now for the gun.
|
||||||
int gunSlotIndex = skeleton.FindSlotIndex(gunSlot);
|
int gunSlotIndex = skeleton.FindSlotIndex(gunSlot);
|
||||||
Attachment templateGun = templateSkin.GetAttachment(gunSlotIndex, gunKey); // STEP 1.1
|
Attachment templateGun = templateSkin.GetAttachment(gunSlotIndex, gunKey); // STEP 1.1
|
||||||
Attachment newGun = templateGun.GetRemappedClone(gunSprite, sourceMaterial); // STEP 1.2 - 1.3
|
Attachment newGun = templateGun.GetRemappedClone(gunSprite, sourceMaterial, pivotShiftsMeshUVCoords: false); // STEP 1.2 - 1.3
|
||||||
if (newGun != null) customSkin.SetAttachment(gunSlotIndex, gunKey, newGun); // STEP 1.4
|
if (newGun != null) customSkin.SetAttachment(gunSlotIndex, gunKey, newGun); // STEP 1.4
|
||||||
|
|
||||||
// customSkin.RemoveAttachment(gunSlotIndex, gunKey); // To remove an item.
|
// customSkin.RemoveAttachment(gunSlotIndex, gunKey); // To remove an item.
|
||||||
|
|||||||
@ -115,6 +115,8 @@ namespace Spine.Unity {
|
|||||||
nameTable.Clear();
|
nameTable.Clear();
|
||||||
|
|
||||||
var skeleton = skeletonRenderer.skeleton;
|
var skeleton = skeletonRenderer.skeleton;
|
||||||
|
if (skeleton == null)
|
||||||
|
return;
|
||||||
slot = skeleton.FindSlot(slotName);
|
slot = skeleton.FindSlot(slotName);
|
||||||
int slotIndex = skeleton.FindSlotIndex(slotName);
|
int slotIndex = skeleton.FindSlotIndex(slotName);
|
||||||
|
|
||||||
|
|||||||
@ -115,6 +115,8 @@ namespace Spine.Unity {
|
|||||||
nameTable.Clear();
|
nameTable.Clear();
|
||||||
|
|
||||||
var skeleton = skeletonGraphic.Skeleton;
|
var skeleton = skeletonGraphic.Skeleton;
|
||||||
|
if (skeleton == null)
|
||||||
|
return;
|
||||||
slot = skeleton.FindSlot(slotName);
|
slot = skeleton.FindSlot(slotName);
|
||||||
int slotIndex = skeleton.FindSlotIndex(slotName);
|
int slotIndex = skeleton.FindSlotIndex(slotName);
|
||||||
|
|
||||||
|
|||||||
@ -45,8 +45,18 @@ namespace Spine.Unity.AttachmentTools {
|
|||||||
/// <param name="premultiplyAlpha">If <c>true</c>, a premultiply alpha clone of the original texture will be created.</param>
|
/// <param name="premultiplyAlpha">If <c>true</c>, a premultiply alpha clone of the original texture will be created.</param>
|
||||||
/// <param name="cloneMeshAsLinked">If <c>true</c> MeshAttachments will be cloned as linked meshes and will inherit animation from the original attachment.</param>
|
/// <param name="cloneMeshAsLinked">If <c>true</c> MeshAttachments will be cloned as linked meshes and will inherit animation from the original attachment.</param>
|
||||||
/// <param name="useOriginalRegionSize">If <c>true</c> the size of the original attachment will be followed, instead of using the Sprite size.</param>
|
/// <param name="useOriginalRegionSize">If <c>true</c> the size of the original attachment will be followed, instead of using the Sprite size.</param>
|
||||||
public static Attachment GetRemappedClone (this Attachment o, Sprite sprite, Material sourceMaterial, bool premultiplyAlpha = true, bool cloneMeshAsLinked = true, bool useOriginalRegionSize = false) {
|
/// <param name="pivotShiftsMeshUVCoords">If <c>true</c> and the original Attachment is a MeshAttachment, then
|
||||||
|
/// a non-central sprite pivot will shift uv coords in the opposite direction. Vertices will not be offset in
|
||||||
|
/// any case when the original Attachment is a MeshAttachment.</param>
|
||||||
|
public static Attachment GetRemappedClone (this Attachment o, Sprite sprite, Material sourceMaterial,
|
||||||
|
bool premultiplyAlpha = true, bool cloneMeshAsLinked = true, bool useOriginalRegionSize = false,
|
||||||
|
bool pivotShiftsMeshUVCoords = true) {
|
||||||
var atlasRegion = premultiplyAlpha ? sprite.ToAtlasRegionPMAClone(sourceMaterial) : sprite.ToAtlasRegion(new Material(sourceMaterial) { mainTexture = sprite.texture } );
|
var atlasRegion = premultiplyAlpha ? sprite.ToAtlasRegionPMAClone(sourceMaterial) : sprite.ToAtlasRegion(new Material(sourceMaterial) { mainTexture = sprite.texture } );
|
||||||
|
if (!pivotShiftsMeshUVCoords && o is MeshAttachment) {
|
||||||
|
// prevent non-central sprite pivot setting offsetX/Y and shifting uv coords out of mesh bounds
|
||||||
|
atlasRegion.offsetX = 0;
|
||||||
|
atlasRegion.offsetY = 0;
|
||||||
|
}
|
||||||
return o.GetRemappedClone(atlasRegion, cloneMeshAsLinked, useOriginalRegionSize, 1f/sprite.pixelsPerUnit);
|
return o.GetRemappedClone(atlasRegion, cloneMeshAsLinked, useOriginalRegionSize, 1f/sprite.pixelsPerUnit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,195 +1,195 @@
|
|||||||
/******************************************************************************
|
/******************************************************************************
|
||||||
* Spine Runtimes License Agreement
|
* Spine Runtimes License Agreement
|
||||||
* Last updated January 1, 2020. Replaces all prior versions.
|
* Last updated January 1, 2020. Replaces all prior versions.
|
||||||
*
|
*
|
||||||
* Copyright (c) 2013-2020, Esoteric Software LLC
|
* Copyright (c) 2013-2020, Esoteric Software LLC
|
||||||
*
|
*
|
||||||
* Integration of the Spine Runtimes into software or otherwise creating
|
* Integration of the Spine Runtimes into software or otherwise creating
|
||||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||||
* http://esotericsoftware.com/spine-editor-license
|
* http://esotericsoftware.com/spine-editor-license
|
||||||
*
|
*
|
||||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||||
* "Products"), provided that each user of the Products must obtain their own
|
* "Products"), provided that each user of the Products must obtain their own
|
||||||
* Spine Editor license and redistribution of the Products in any form must
|
* Spine Editor license and redistribution of the Products in any form must
|
||||||
* include this license and copyright notice.
|
* include this license and copyright notice.
|
||||||
*
|
*
|
||||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Microsoft.Xna.Framework.Graphics;
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
|
|
||||||
namespace Spine {
|
namespace Spine {
|
||||||
public struct VertexPositionColorTextureColor : IVertexType {
|
public struct VertexPositionColorTextureColor : IVertexType {
|
||||||
public Vector3 Position;
|
public Vector3 Position;
|
||||||
public Color Color;
|
public Color Color;
|
||||||
public Vector2 TextureCoordinate;
|
public Vector2 TextureCoordinate;
|
||||||
public Color Color2;
|
public Color Color2;
|
||||||
|
|
||||||
public readonly static VertexDeclaration VertexDeclaration = new VertexDeclaration
|
public readonly static VertexDeclaration VertexDeclaration = new VertexDeclaration
|
||||||
(
|
(
|
||||||
new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0),
|
new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0),
|
||||||
new VertexElement(12, VertexElementFormat.Color, VertexElementUsage.Color, 0),
|
new VertexElement(12, VertexElementFormat.Color, VertexElementUsage.Color, 0),
|
||||||
new VertexElement(16, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0),
|
new VertexElement(16, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0),
|
||||||
new VertexElement(24, VertexElementFormat.Color, VertexElementUsage.Color, 1)
|
new VertexElement(24, VertexElementFormat.Color, VertexElementUsage.Color, 1)
|
||||||
);
|
);
|
||||||
|
|
||||||
VertexDeclaration IVertexType.VertexDeclaration {
|
VertexDeclaration IVertexType.VertexDeclaration {
|
||||||
get { return VertexDeclaration; }
|
get { return VertexDeclaration; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// #region License
|
// #region License
|
||||||
// /*
|
// /*
|
||||||
// Microsoft Public License (Ms-PL)
|
// Microsoft Public License (Ms-PL)
|
||||||
// MonoGame - Copyright <20> 2009 The MonoGame Team
|
// MonoGame - Copyright <20> 2009 The MonoGame Team
|
||||||
//
|
//
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
//
|
//
|
||||||
// This license governs use of the accompanying software. If you use the software, you accept this license. If you do not
|
// This license governs use of the accompanying software. If you use the software, you accept this license. If you do not
|
||||||
// accept the license, do not use the software.
|
// accept the license, do not use the software.
|
||||||
//
|
//
|
||||||
// 1. Definitions
|
// 1. Definitions
|
||||||
// The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under
|
// The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under
|
||||||
// U.S. copyright law.
|
// U.S. copyright law.
|
||||||
//
|
//
|
||||||
// A "contribution" is the original software, or any additions or changes to the software.
|
// A "contribution" is the original software, or any additions or changes to the software.
|
||||||
// A "contributor" is any person that distributes its contribution under this license.
|
// A "contributor" is any person that distributes its contribution under this license.
|
||||||
// "Licensed patents" are a contributor's patent claims that read directly on its contribution.
|
// "Licensed patents" are a contributor's patent claims that read directly on its contribution.
|
||||||
//
|
//
|
||||||
// 2. Grant of Rights
|
// 2. Grant of Rights
|
||||||
// (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3,
|
// (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3,
|
||||||
// each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create.
|
// each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create.
|
||||||
// (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3,
|
// (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3,
|
||||||
// each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software.
|
// each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software.
|
||||||
//
|
//
|
||||||
// 3. Conditions and Limitations
|
// 3. Conditions and Limitations
|
||||||
// (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks.
|
// (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks.
|
||||||
// (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software,
|
// (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software,
|
||||||
// your patent license from such contributor to the software ends automatically.
|
// your patent license from such contributor to the software ends automatically.
|
||||||
// (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution
|
// (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution
|
||||||
// notices that are present in the software.
|
// notices that are present in the software.
|
||||||
// (D) If you distribute any portion of the software in source code form, you may do so only under this license by including
|
// (D) If you distribute any portion of the software in source code form, you may do so only under this license by including
|
||||||
// a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object
|
// a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object
|
||||||
// code form, you may only do so under a license that complies with this license.
|
// code form, you may only do so under a license that complies with this license.
|
||||||
// (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees
|
// (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees
|
||||||
// or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent
|
// or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent
|
||||||
// permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular
|
// permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular
|
||||||
// purpose and non-infringement.
|
// purpose and non-infringement.
|
||||||
// */
|
// */
|
||||||
// #endregion License
|
// #endregion License
|
||||||
//
|
//
|
||||||
|
|
||||||
/// <summary>Draws batched meshes.</summary>
|
/// <summary>Draws batched meshes.</summary>
|
||||||
public class MeshBatcher {
|
public class MeshBatcher {
|
||||||
private readonly List<MeshItem> items;
|
private readonly List<MeshItem> items;
|
||||||
private readonly Queue<MeshItem> freeItems;
|
private readonly Queue<MeshItem> freeItems;
|
||||||
private VertexPositionColorTextureColor[] vertexArray = { };
|
private VertexPositionColorTextureColor[] vertexArray = { };
|
||||||
private short[] triangles = { };
|
private short[] triangles = { };
|
||||||
|
|
||||||
public MeshBatcher () {
|
public MeshBatcher () {
|
||||||
items = new List<MeshItem>(256);
|
items = new List<MeshItem>(256);
|
||||||
freeItems = new Queue<MeshItem>(256);
|
freeItems = new Queue<MeshItem>(256);
|
||||||
EnsureCapacity(256, 512);
|
EnsureCapacity(256, 512);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Returns a pooled MeshItem.</summary>
|
/// <summary>Returns a pooled MeshItem.</summary>
|
||||||
public MeshItem NextItem (int vertexCount, int triangleCount) {
|
public MeshItem NextItem (int vertexCount, int triangleCount) {
|
||||||
MeshItem item = freeItems.Count > 0 ? freeItems.Dequeue() : new MeshItem();
|
MeshItem item = freeItems.Count > 0 ? freeItems.Dequeue() : new MeshItem();
|
||||||
if (item.vertices.Length < vertexCount) item.vertices = new VertexPositionColorTextureColor[vertexCount];
|
if (item.vertices.Length < vertexCount) item.vertices = new VertexPositionColorTextureColor[vertexCount];
|
||||||
if (item.triangles.Length < triangleCount) item.triangles = new int[triangleCount];
|
if (item.triangles.Length < triangleCount) item.triangles = new int[triangleCount];
|
||||||
item.vertexCount = vertexCount;
|
item.vertexCount = vertexCount;
|
||||||
item.triangleCount = triangleCount;
|
item.triangleCount = triangleCount;
|
||||||
items.Add(item);
|
items.Add(item);
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void EnsureCapacity (int vertexCount, int triangleCount) {
|
private void EnsureCapacity (int vertexCount, int triangleCount) {
|
||||||
if (vertexArray.Length < vertexCount) vertexArray = new VertexPositionColorTextureColor[vertexCount];
|
if (vertexArray.Length < vertexCount) vertexArray = new VertexPositionColorTextureColor[vertexCount];
|
||||||
if (triangles.Length < triangleCount) triangles = new short[triangleCount];
|
if (triangles.Length < triangleCount) triangles = new short[triangleCount];
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Draw (GraphicsDevice device) {
|
public void Draw (GraphicsDevice device) {
|
||||||
if (items.Count == 0) return;
|
if (items.Count == 0) return;
|
||||||
|
|
||||||
int itemCount = items.Count;
|
int itemCount = items.Count;
|
||||||
int vertexCount = 0, triangleCount = 0;
|
int vertexCount = 0, triangleCount = 0;
|
||||||
for (int i = 0; i < itemCount; i++) {
|
for (int i = 0; i < itemCount; i++) {
|
||||||
MeshItem item = items[i];
|
MeshItem item = items[i];
|
||||||
vertexCount += item.vertexCount;
|
vertexCount += item.vertexCount;
|
||||||
triangleCount += item.triangleCount;
|
triangleCount += item.triangleCount;
|
||||||
}
|
}
|
||||||
EnsureCapacity(vertexCount, triangleCount);
|
EnsureCapacity(vertexCount, triangleCount);
|
||||||
|
|
||||||
vertexCount = 0;
|
vertexCount = 0;
|
||||||
triangleCount = 0;
|
triangleCount = 0;
|
||||||
Texture2D lastTexture = null;
|
Texture2D lastTexture = null;
|
||||||
for (int i = 0; i < itemCount; i++) {
|
for (int i = 0; i < itemCount; i++) {
|
||||||
MeshItem item = items[i];
|
MeshItem item = items[i];
|
||||||
int itemVertexCount = item.vertexCount;
|
int itemVertexCount = item.vertexCount;
|
||||||
|
|
||||||
if (item.texture != lastTexture || vertexCount + itemVertexCount > short.MaxValue) {
|
if (item.texture != lastTexture || vertexCount + itemVertexCount > short.MaxValue) {
|
||||||
FlushVertexArray(device, vertexCount, triangleCount);
|
FlushVertexArray(device, vertexCount, triangleCount);
|
||||||
vertexCount = 0;
|
vertexCount = 0;
|
||||||
triangleCount = 0;
|
triangleCount = 0;
|
||||||
lastTexture = item.texture;
|
lastTexture = item.texture;
|
||||||
device.Textures[0] = lastTexture;
|
device.Textures[0] = lastTexture;
|
||||||
if (item.textureLayers != null) {
|
if (item.textureLayers != null) {
|
||||||
for (int layer = 1; layer < item.textureLayers.Length; ++layer)
|
for (int layer = 1; layer < item.textureLayers.Length; ++layer)
|
||||||
device.Textures[layer] = item.textureLayers[layer];
|
device.Textures[layer] = item.textureLayers[layer];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int[] itemTriangles = item.triangles;
|
int[] itemTriangles = item.triangles;
|
||||||
int itemTriangleCount = item.triangleCount;
|
int itemTriangleCount = item.triangleCount;
|
||||||
for (int ii = 0, t = triangleCount; ii < itemTriangleCount; ii++, t++)
|
for (int ii = 0, t = triangleCount; ii < itemTriangleCount; ii++, t++)
|
||||||
triangles[t] = (short)(itemTriangles[ii] + vertexCount);
|
triangles[t] = (short)(itemTriangles[ii] + vertexCount);
|
||||||
triangleCount += itemTriangleCount;
|
triangleCount += itemTriangleCount;
|
||||||
|
|
||||||
Array.Copy(item.vertices, 0, vertexArray, vertexCount, itemVertexCount);
|
Array.Copy(item.vertices, 0, vertexArray, vertexCount, itemVertexCount);
|
||||||
vertexCount += itemVertexCount;
|
vertexCount += itemVertexCount;
|
||||||
}
|
}
|
||||||
FlushVertexArray(device, vertexCount, triangleCount);
|
FlushVertexArray(device, vertexCount, triangleCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AfterLastDrawPass () {
|
public void AfterLastDrawPass () {
|
||||||
int itemCount = items.Count;
|
int itemCount = items.Count;
|
||||||
for (int i = 0; i < itemCount; i++) {
|
for (int i = 0; i < itemCount; i++) {
|
||||||
var item = items[i];
|
var item = items[i];
|
||||||
item.texture = null;
|
item.texture = null;
|
||||||
freeItems.Enqueue(item);
|
freeItems.Enqueue(item);
|
||||||
}
|
}
|
||||||
items.Clear();
|
items.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void FlushVertexArray (GraphicsDevice device, int vertexCount, int triangleCount) {
|
private void FlushVertexArray (GraphicsDevice device, int vertexCount, int triangleCount) {
|
||||||
if (vertexCount == 0) return;
|
if (vertexCount == 0) return;
|
||||||
device.DrawUserIndexedPrimitives(
|
device.DrawUserIndexedPrimitives(
|
||||||
PrimitiveType.TriangleList,
|
PrimitiveType.TriangleList,
|
||||||
vertexArray, 0, vertexCount,
|
vertexArray, 0, vertexCount,
|
||||||
triangles, 0, triangleCount / 3,
|
triangles, 0, triangleCount / 3,
|
||||||
VertexPositionColorTextureColor.VertexDeclaration);
|
VertexPositionColorTextureColor.VertexDeclaration);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class MeshItem {
|
public class MeshItem {
|
||||||
public Texture2D texture = null;
|
public Texture2D texture = null;
|
||||||
public Texture2D[] textureLayers = null;
|
public Texture2D[] textureLayers = null;
|
||||||
public int vertexCount, triangleCount;
|
public int vertexCount, triangleCount;
|
||||||
public VertexPositionColorTextureColor[] vertices = { };
|
public VertexPositionColorTextureColor[] vertices = { };
|
||||||
public int[] triangles = { };
|
public int[] triangles = { };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,79 +1,79 @@
|
|||||||
/******************************************************************************
|
/******************************************************************************
|
||||||
* Spine Runtimes License Agreement
|
* Spine Runtimes License Agreement
|
||||||
* Last updated January 1, 2020. Replaces all prior versions.
|
* Last updated January 1, 2020. Replaces all prior versions.
|
||||||
*
|
*
|
||||||
* Copyright (c) 2013-2020, Esoteric Software LLC
|
* Copyright (c) 2013-2020, Esoteric Software LLC
|
||||||
*
|
*
|
||||||
* Integration of the Spine Runtimes into software or otherwise creating
|
* Integration of the Spine Runtimes into software or otherwise creating
|
||||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||||
* http://esotericsoftware.com/spine-editor-license
|
* http://esotericsoftware.com/spine-editor-license
|
||||||
*
|
*
|
||||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||||
* "Products"), provided that each user of the Products must obtain their own
|
* "Products"), provided that each user of the Products must obtain their own
|
||||||
* Spine Editor license and redistribution of the Products in any form must
|
* Spine Editor license and redistribution of the Products in any form must
|
||||||
* include this license and copyright notice.
|
* include this license and copyright notice.
|
||||||
*
|
*
|
||||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using Microsoft.Xna.Framework.Graphics;
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
|
||||||
#if WINDOWS_STOREAPP
|
#if WINDOWS_STOREAPP
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Windows.Storage;
|
using Windows.Storage;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace Spine {
|
namespace Spine {
|
||||||
|
|
||||||
static public class Util {
|
static public class Util {
|
||||||
#if WINDOWS_STOREAPP
|
#if WINDOWS_STOREAPP
|
||||||
private static async Task<Texture2D> LoadFile(GraphicsDevice device, String path) {
|
private static async Task<Texture2D> LoadFile(GraphicsDevice device, String path) {
|
||||||
var folder = Windows.ApplicationModel.Package.Current.InstalledLocation;
|
var folder = Windows.ApplicationModel.Package.Current.InstalledLocation;
|
||||||
var file = await folder.GetFileAsync(path).AsTask().ConfigureAwait(false);
|
var file = await folder.GetFileAsync(path).AsTask().ConfigureAwait(false);
|
||||||
try {
|
try {
|
||||||
return Util.LoadTexture(device, await file.OpenStreamForReadAsync().ConfigureAwait(false));
|
return Util.LoadTexture(device, await file.OpenStreamForReadAsync().ConfigureAwait(false));
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
throw new Exception("Error reading texture file: " + path, ex);
|
throw new Exception("Error reading texture file: " + path, ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static public Texture2D LoadTexture (GraphicsDevice device, String path) {
|
static public Texture2D LoadTexture (GraphicsDevice device, String path) {
|
||||||
return LoadFile(device, path).Result;
|
return LoadFile(device, path).Result;
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
static public Texture2D LoadTexture (GraphicsDevice device, String path) {
|
static public Texture2D LoadTexture (GraphicsDevice device, String path) {
|
||||||
|
|
||||||
#if WINDOWS_PHONE
|
#if WINDOWS_PHONE
|
||||||
Stream stream = Microsoft.Xna.Framework.TitleContainer.OpenStream(path);
|
Stream stream = Microsoft.Xna.Framework.TitleContainer.OpenStream(path);
|
||||||
using (Stream input = stream) {
|
using (Stream input = stream) {
|
||||||
#else
|
#else
|
||||||
using (Stream input = new FileStream(path, FileMode.Open, FileAccess.Read)) {
|
using (Stream input = new FileStream(path, FileMode.Open, FileAccess.Read)) {
|
||||||
#endif
|
#endif
|
||||||
try {
|
try {
|
||||||
return Util.LoadTexture(device, input);
|
return Util.LoadTexture(device, input);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
throw new Exception("Error reading texture file: " + path, ex);
|
throw new Exception("Error reading texture file: " + path, ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static public Texture2D LoadTexture (GraphicsDevice device, Stream input) {
|
static public Texture2D LoadTexture (GraphicsDevice device, Stream input) {
|
||||||
return Texture2D.FromStream(device, input);
|
return Texture2D.FromStream(device, input);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,94 +1,94 @@
|
|||||||
/******************************************************************************
|
/******************************************************************************
|
||||||
* Spine Runtimes License Agreement
|
* Spine Runtimes License Agreement
|
||||||
* Last updated January 1, 2020. Replaces all prior versions.
|
* Last updated January 1, 2020. Replaces all prior versions.
|
||||||
*
|
*
|
||||||
* Copyright (c) 2013-2020, Esoteric Software LLC
|
* Copyright (c) 2013-2020, Esoteric Software LLC
|
||||||
*
|
*
|
||||||
* Integration of the Spine Runtimes into software or otherwise creating
|
* Integration of the Spine Runtimes into software or otherwise creating
|
||||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||||
* http://esotericsoftware.com/spine-editor-license
|
* http://esotericsoftware.com/spine-editor-license
|
||||||
*
|
*
|
||||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||||
* "Products"), provided that each user of the Products must obtain their own
|
* "Products"), provided that each user of the Products must obtain their own
|
||||||
* Spine Editor license and redistribution of the Products in any form must
|
* Spine Editor license and redistribution of the Products in any form must
|
||||||
* include this license and copyright notice.
|
* include this license and copyright notice.
|
||||||
*
|
*
|
||||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using Microsoft.Xna.Framework.Graphics;
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
|
||||||
namespace Spine {
|
namespace Spine {
|
||||||
public class XnaTextureLoader : TextureLoader {
|
public class XnaTextureLoader : TextureLoader {
|
||||||
GraphicsDevice device;
|
GraphicsDevice device;
|
||||||
string[] textureLayerSuffixes = null;
|
string[] textureLayerSuffixes = null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructor.
|
/// Constructor.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="device">The graphics device to be used.</param>
|
/// <param name="device">The graphics device to be used.</param>
|
||||||
/// <param name="loadMultipleTextureLayers">If <c>true</c> multiple textures layers
|
/// <param name="loadMultipleTextureLayers">If <c>true</c> multiple textures layers
|
||||||
/// (e.g. a diffuse/albedo texture and a normal map) are loaded instead of a single texture.
|
/// (e.g. a diffuse/albedo texture and a normal map) are loaded instead of a single texture.
|
||||||
/// Names are constructed based on suffixes added according to the <c>textureSuffixes</c> parameter.</param>
|
/// Names are constructed based on suffixes added according to the <c>textureSuffixes</c> parameter.</param>
|
||||||
/// <param name="textureSuffixes">If <c>loadMultipleTextureLayers</c> is <c>true</c>, the strings of this array
|
/// <param name="textureSuffixes">If <c>loadMultipleTextureLayers</c> is <c>true</c>, the strings of this array
|
||||||
/// define the path name suffix of each layer to be loaded. Array size must be equal to the number of layers to be loaded.
|
/// define the path name suffix of each layer to be loaded. Array size must be equal to the number of layers to be loaded.
|
||||||
/// The first array entry is the suffix to be <c>replaced</c> (e.g. "_albedo", or "" for a first layer without a suffix),
|
/// The first array entry is the suffix to be <c>replaced</c> (e.g. "_albedo", or "" for a first layer without a suffix),
|
||||||
/// subsequent array entries contain the suffix to replace the first entry with (e.g. "_normals").
|
/// subsequent array entries contain the suffix to replace the first entry with (e.g. "_normals").
|
||||||
///
|
///
|
||||||
/// An example would be:
|
/// An example would be:
|
||||||
/// <code>new string[] { "", "_normals" }</code> for loading a base diffuse texture named "skeletonname.png" and
|
/// <code>new string[] { "", "_normals" }</code> for loading a base diffuse texture named "skeletonname.png" and
|
||||||
/// a normalmap named "skeletonname_normals.png".</param>
|
/// a normalmap named "skeletonname_normals.png".</param>
|
||||||
public XnaTextureLoader (GraphicsDevice device, bool loadMultipleTextureLayers = false, string[] textureSuffixes = null) {
|
public XnaTextureLoader (GraphicsDevice device, bool loadMultipleTextureLayers = false, string[] textureSuffixes = null) {
|
||||||
this.device = device;
|
this.device = device;
|
||||||
if (loadMultipleTextureLayers)
|
if (loadMultipleTextureLayers)
|
||||||
this.textureLayerSuffixes = textureSuffixes;
|
this.textureLayerSuffixes = textureSuffixes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Load (AtlasPage page, String path) {
|
public void Load (AtlasPage page, String path) {
|
||||||
Texture2D texture = Util.LoadTexture(device, path);
|
Texture2D texture = Util.LoadTexture(device, path);
|
||||||
page.width = texture.Width;
|
page.width = texture.Width;
|
||||||
page.height = texture.Height;
|
page.height = texture.Height;
|
||||||
|
|
||||||
if (textureLayerSuffixes == null) {
|
if (textureLayerSuffixes == null) {
|
||||||
page.rendererObject = texture;
|
page.rendererObject = texture;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Texture2D[] textureLayersArray = new Texture2D[textureLayerSuffixes.Length];
|
Texture2D[] textureLayersArray = new Texture2D[textureLayerSuffixes.Length];
|
||||||
textureLayersArray[0] = texture;
|
textureLayersArray[0] = texture;
|
||||||
for (int layer = 1; layer < textureLayersArray.Length; ++layer) {
|
for (int layer = 1; layer < textureLayersArray.Length; ++layer) {
|
||||||
string layerPath = GetLayerName(path, textureLayerSuffixes[0], textureLayerSuffixes[layer]);
|
string layerPath = GetLayerName(path, textureLayerSuffixes[0], textureLayerSuffixes[layer]);
|
||||||
textureLayersArray[layer] = Util.LoadTexture(device, layerPath);
|
textureLayersArray[layer] = Util.LoadTexture(device, layerPath);
|
||||||
}
|
}
|
||||||
page.rendererObject = textureLayersArray;
|
page.rendererObject = textureLayersArray;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Unload (Object texture) {
|
public void Unload (Object texture) {
|
||||||
((Texture2D)texture).Dispose();
|
((Texture2D)texture).Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetLayerName (string firstLayerPath, string firstLayerSuffix, string replacementSuffix) {
|
private string GetLayerName (string firstLayerPath, string firstLayerSuffix, string replacementSuffix) {
|
||||||
|
|
||||||
int suffixLocation = firstLayerPath.LastIndexOf(firstLayerSuffix + ".");
|
int suffixLocation = firstLayerPath.LastIndexOf(firstLayerSuffix + ".");
|
||||||
if (suffixLocation == -1) {
|
if (suffixLocation == -1) {
|
||||||
throw new Exception(string.Concat("Error composing texture layer name: first texture layer name '", firstLayerPath,
|
throw new Exception(string.Concat("Error composing texture layer name: first texture layer name '", firstLayerPath,
|
||||||
"' does not contain suffix to be replaced: '", firstLayerSuffix, "'"));
|
"' does not contain suffix to be replaced: '", firstLayerSuffix, "'"));
|
||||||
}
|
}
|
||||||
return firstLayerPath.Remove(suffixLocation, firstLayerSuffix.Length).Insert(suffixLocation, replacementSuffix);
|
return firstLayerPath.Remove(suffixLocation, firstLayerSuffix.Length).Insert(suffixLocation, replacementSuffix);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user