Move Common functionality to Core project & rendering and Model support as well

This commit is contained in:
miku-666
2025-09-01 23:03:39 +02:00
parent 698056a0a0
commit 9656c8b48d
177 changed files with 5979 additions and 1279 deletions

View File

@@ -0,0 +1,24 @@
using System.Drawing;
using AnimatedGif;
namespace PckStudio.Core.Extensions
{
public static class AnimationExtensions
{
public static Image CreateAnimationImage(this Animation animation)
{
if (animation.FrameCount == 0)
{
return null;
}
var ms = new System.IO.MemoryStream();
var generateor = new AnimatedGifCreator(ms, GameConstants.GameTickInMilliseconds, 0);
foreach (Animation.Frame frame in animation.GetInterpolatedFrames())
{
generateor.AddFrame(frame.Texture, frame.Ticks * GameConstants.GameTickInMilliseconds, GifQuality.Bit8);
}
ms.Position = 0;
return Image.FromStream(ms);
}
}
}

View File

@@ -0,0 +1,14 @@
namespace PckStudio.Core.Extensions
{
public enum BlendMode
{
Add,
Subtract,
Multiply,
Average,
DescendingOrder,
AscendingOrder,
Screen,
Overlay
}
}

View File

@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PckStudio.Core.Extensions
{
public static class BoundingBoxExtensions
{
public static BoundingBox GetEnclosingBoundingBox(this IEnumerable<BoundingBox> boundingBoxes)
{
return boundingBoxes.DefaultIfEmpty().Aggregate((a, b) => new BoundingBox(OpenTK.Vector3.ComponentMin(a.Start, b.Start), OpenTK.Vector3.ComponentMax(a.End, b.End)));
}
}
}

View File

@@ -0,0 +1,73 @@
using System.Drawing;
using System.Numerics;
namespace PckStudio.Core.Extensions
{
public static class ColorExtensions
{
/// <summary>
/// Normalizes the Color between 0.0 - 1.0
/// </summary>
/// <returns></returns>
public static Vector4 Normalize(this Color color)
{
return new Vector4(color.R / 255f, color.G / 255f, color.B / 255f, color.A / 255f);
}
public static Color Inversed(this Color color)
{
return Color.FromArgb(color.A, 255 - color.R, 255 - color.G, 255 - color.B);
}
public static Color GreyScaled(this Color color)
{
int greyScaleValue = (color.R + color.G + color.B) / 3;
return Color.FromArgb(color.A, greyScaleValue, greyScaleValue, greyScaleValue);
}
public static int ToBGR(this Color color)
{
return color.B << 16 | color.G << 8 | color.R;
}
public static byte BlendValues(byte source, byte overlay, BlendMode blendType)
{
return (byte)MathExtensions.Clamp(BlendValues(source / 255f, overlay / 255f, blendType) * 255, 0, 255);
}
public static float BlendValues(float source, float overlay, BlendMode blendType)
{
source = MathExtensions.Clamp(source, 0.0f, 1.0f);
overlay = MathExtensions.Clamp(overlay, 0.0f, 1.0f);
float resultValue = blendType switch
{
BlendMode.Add => source + overlay,
BlendMode.Subtract => source - overlay,
BlendMode.Multiply => source * overlay,
BlendMode.Average => (source + overlay) / 2.0f,
BlendMode.AscendingOrder => source > overlay ? overlay : source,
BlendMode.DescendingOrder => source < overlay ? overlay : source,
BlendMode.Screen => 1f - (1f - source) * (1f - overlay),
BlendMode.Overlay => source < 0.5f ? 2f * source * overlay : 1f - 2f * (1f - source) * (1f - overlay),
_ => 0.0f
};
return MathExtensions.Clamp(resultValue, 0.0f, 1.0f);
}
public static byte Mix(double ratio, byte val1, byte val2)
{
ratio = MathExtensions.Clamp(ratio, 0.0, 1.0);
return (byte)(ratio * val1 + (1.0 - ratio) * val2);
}
public static Color Mix(this Color c1, Color c2, double ratio)
{
ratio = MathExtensions.Clamp(ratio, 0.0, 1.0);
return Color.FromArgb(c1.A,
Mix(ratio, c1.R, c2.R),
Mix(ratio, c1.G, c2.G),
Mix(ratio, c1.B, c2.B)
);
}
}
}

View File

@@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace PckStudio.Core.Extensions
{
public static class CursorExtensions
{
[StructLayout(LayoutKind.Sequential)]
struct PointStruct
{
public Int32 x;
public Int32 y;
}
[StructLayout(LayoutKind.Sequential)]
struct CursorInfoStruct
{
/// <summary> The structure size in bytes that must be set via calling Marshal.SizeOf(typeof(CursorInfoStruct)).</summary>
public Int32 cbSize;
/// <summary> The cursor state: 0 == hidden, 1 == showing, 2 == suppressed (is supposed to be when finger touch is used, but in practice finger touch results in 0, not 2)</summary>
public Int32 flags;
/// <summary> A handle to the cursor. </summary>
public IntPtr hCursor;
/// <summary> The cursor screen coordinates.</summary>
public PointStruct pt;
}
/// <summary> Must initialize cbSize</summary>
[DllImport("user32.dll")]
static extern bool GetCursorInfo(ref CursorInfoStruct pci);
public static bool IsVisible(this Cursor _)
{
CursorInfoStruct pci = new CursorInfoStruct();
pci.cbSize = Marshal.SizeOf(typeof(CursorInfoStruct));
GetCursorInfo(ref pci);
// const Int32 hidden = 0x00;
const Int32 showing = 0x01;
// const Int32 suppressed = 0x02;
bool isVisible = ((pci.flags & showing) != 0);
return isVisible;
}
}
}

View File

@@ -0,0 +1,38 @@
using System.Collections.Generic;
using System.Linq;
namespace PckStudio.Core.Extensions
{
public static class EnumerableExtensions
{
public static IEnumerable<(int index, T value)>enumerate<T>(this IEnumerable<T> array)
{
int i = 0;
foreach (T item in array)
{
yield return (i++, item);
}
yield break;
}
public static bool EqualsAny<T>(this T type, params T[] items)
{
foreach (T item in items)
{
if (item.Equals(type))
return true;
}
return false;
}
public static bool ContainsAny<T>(this IEnumerable<T> array, params T[] items)
{
foreach (T item in array)
{
if (items.Contains(item))
return true;
}
return false;
}
}
}

View File

@@ -0,0 +1,44 @@
using System.Drawing;
using System.Drawing.Drawing2D;
namespace PckStudio.Core.Extensions
{
public struct GraphicsConfig
{
public GraphicsConfig()
{
CompositingQuality = default;
InterpolationMode = default;
SmoothingMode = default;
PixelOffsetMode = default;
CompositingMode = default;
}
public CompositingMode CompositingMode { get; set; }
public CompositingQuality CompositingQuality { get; set; }
public InterpolationMode InterpolationMode { get; set; }
public SmoothingMode SmoothingMode { get; set; }
public PixelOffsetMode PixelOffsetMode { get; set; }
}
public static class GraphicsExtensions
{
public static void ApplyConfig(this Graphics graphics, GraphicsConfig config)
{
graphics.CompositingMode = config.CompositingMode;
graphics.CompositingQuality = config.CompositingQuality;
graphics.InterpolationMode = config.InterpolationMode;
graphics.SmoothingMode = config.SmoothingMode;
graphics.PixelOffsetMode = config.PixelOffsetMode;
}
public static Graphics Fill(this Graphics graphics, Rectangle area, Color color)
{
Region clip = graphics.Clip;
graphics.SetClip(area, CombineMode.Replace);
graphics.Clear(color);
graphics.SetClip(clip, CombineMode.Replace);
return graphics;
}
}
}

View File

@@ -0,0 +1,273 @@
/* Copyright (c) 2023-present miku-666
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1.The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
**/
using System;
using System.Drawing;
using System.Diagnostics;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
namespace PckStudio.Core.Extensions
{
public static class ImageExtensions
{
public static Image ReleaseFromFile(this Image image)
{
Image img = new Bitmap(image);
image.Dispose();
return img;
}
public static Image GetArea(this Image source, Rectangle area)
{
Image tileImage = new Bitmap(area.Width, area.Height);
using (Graphics gfx = Graphics.FromImage(tileImage))
{
gfx.SmoothingMode = SmoothingMode.None;
gfx.InterpolationMode = InterpolationMode.NearestNeighbor;
gfx.PixelOffsetMode = PixelOffsetMode.HighQuality;
gfx.DrawImage(source, new Rectangle(Point.Empty, area.Size), area, GraphicsUnit.Pixel);
}
return tileImage;
}
/// <summary>
/// Creates an IEnumerable by reading in horizontal order
/// </summary>
/// <param name="source">this image</param>
/// <param name="scalar">Indecates width and height of image sub section</param>
/// <returns><see cref="IEnumerable{Image}"/> of type <see cref="Image"/></returns>
public static IEnumerable<Image> SplitHorizontal(this Image source, int scalar)
{
return source.Split(scalar, ImageLayoutDirection.Horizontal);
}
public static IEnumerable<Image> Split(this Image source, int scalar, ImageLayoutDirection layoutDirection)
{
return Split(source, new Size(scalar, scalar), layoutDirection);
}
public static IEnumerable<Image> Split(this Image source, Size size, ImageLayoutDirection imageLayout)
{
int rowCount = source.Width / size.Width;
int columnCount = source.Height / size.Height;
Debug.WriteLine($"Image size: {source.Size}, Area size: {size}, col num: {columnCount}, row num: {rowCount}");
for (int i = 0; i < columnCount * rowCount; i++)
{
int row = Math.DivRem(i, rowCount, out int column);
if (imageLayout == ImageLayoutDirection.Vertical)
column = Math.DivRem(i, columnCount, out row);
Rectangle tileArea = new Rectangle(new Point(column * size.Width, row * size.Height), size);
yield return source.GetArea(tileArea);
}
yield break;
}
public static IEnumerable<Image> Split(this Image source, ImageLayoutDirection layoutDirection)
{
for (int i = 0; i < source.Height / source.Width; i++)
{
ImageSection locationInfo = new ImageSection(source.Size, i, layoutDirection);
yield return source.GetArea(locationInfo.Area);
}
yield break;
}
public static Image Combine(this IEnumerable<Image> sources, ImageLayoutDirection layoutDirection)
{
Size imageSize = CalculateImageSize(sources, layoutDirection);
var image = new Bitmap(imageSize.Width, imageSize.Height);
using (var graphic = Graphics.FromImage(image))
{
foreach ((int i, Image texture) in sources.enumerate())
{
var info = new ImageSection(texture.Size, i, layoutDirection);
graphic.DrawImage(texture, info.Point);
}
}
return image;
}
private static Size CalculateImageSize(IEnumerable<Image> sources, ImageLayoutDirection layoutDirection)
{
Size size = sources.First().Size;
int count = sources.Count();
if (count < 2)
return count < 1 ? Size.Empty : size;
var horizontal = layoutDirection == ImageLayoutDirection.Horizontal;
if (!sources.All(img => img.Size == size))
throw new InvalidOperationException("Images must have the same width and height.");
if (horizontal)
size.Width *= count;
else
size.Height *= count;
return size;
}
public static Image Resize(this Image image, Size size, GraphicsConfig graphicsConfig)
{
return image.Resize(size.Width, size.Height, graphicsConfig);
}
public static Image Resize(this Image image, int width, int height, GraphicsConfig graphicsConfig)
{
var destRect = new Rectangle(0, 0, width, height);
var destImage = new Bitmap(width, height);
destImage.SetResolution(image.HorizontalResolution, image.VerticalResolution);
using (var graphics = Graphics.FromImage(destImage))
{
graphics.ApplyConfig(graphicsConfig);
using (var wrapMode = new ImageAttributes())
{
wrapMode.SetWrapMode(WrapMode.TileFlipXY);
graphics.DrawImage(image, destRect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, wrapMode);
}
}
return destImage;
}
public static Image Blend(this Image image, Color overlayColor, BlendMode mode)
{
if (image is not Bitmap baseImage)
return image;
Bitmap bitmapResult = new Bitmap(baseImage.Width, baseImage.Height, PixelFormat.Format32bppArgb);
BitmapData baseImageData = baseImage.LockBits(new Rectangle(Point.Empty, baseImage.Size),
ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
BitmapData resultImageData = bitmapResult.LockBits(new Rectangle(Point.Empty, bitmapResult.Size),
ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
Parallel.For(0, baseImageData.Stride * baseImageData.Height / 4, (i) =>
{
int k = i * 4;
unsafe
{
int color = Unsafe.Read<int>((baseImageData.Scan0 + k).ToPointer());
byte a = (byte)(color >> 24 & 0xff);
if (a == 0)
{
Unsafe.Write((resultImageData.Scan0 + k).ToPointer(), 0);
return;
}
var b = ColorExtensions.BlendValues((byte)(color >> 0 & 0xff), overlayColor.B, mode);
var g = ColorExtensions.BlendValues((byte)(color >> 8 & 0xff), overlayColor.G, mode);
var r = ColorExtensions.BlendValues((byte)(color >> 16 & 0xff), overlayColor.R, mode);
int blendedValue = a << 24 | r << 16 | g << 8 | b;
Unsafe.Write((resultImageData.Scan0 + k).ToPointer(), blendedValue);
}
});
bitmapResult.UnlockBits(resultImageData);
baseImage.UnlockBits(baseImageData);
return bitmapResult;
}
public static Image Blend(this Image image, Image overlay, BlendMode mode)
{
if (image is not Bitmap baseImage || overlay is not Bitmap overlayImage ||
image.Width != overlay.Width || image.Height != overlay.Height)
return image;
BitmapData baseImageData = baseImage.LockBits(new Rectangle(Point.Empty, baseImage.Size),
ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
byte[] baseImageBuffer = new byte[baseImageData.Stride * baseImageData.Height];
Marshal.Copy(baseImageData.Scan0, baseImageBuffer, 0, baseImageBuffer.Length);
BitmapData overlayImageData = overlayImage.LockBits(new Rectangle(Point.Empty, overlayImage.Size),
ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
byte[] overlayImageBuffer = new byte[overlayImageData.Stride * overlayImageData.Height];
Marshal.Copy(overlayImageData.Scan0, overlayImageBuffer, 0, overlayImageBuffer.Length);
for (int k = 0; k < baseImageBuffer.Length && k < overlayImageBuffer.Length; k += 4)
{
baseImageBuffer[k + 0] = ColorExtensions.BlendValues(baseImageBuffer[k + 0], overlayImageBuffer[k + 0], mode);
baseImageBuffer[k + 1] = ColorExtensions.BlendValues(baseImageBuffer[k + 1], overlayImageBuffer[k + 1], mode);
baseImageBuffer[k + 2] = ColorExtensions.BlendValues(baseImageBuffer[k + 2], overlayImageBuffer[k + 2], mode);
}
Bitmap bitmapResult = new Bitmap(baseImage.Width, baseImage.Height, PixelFormat.Format32bppArgb);
BitmapData resultImageData = bitmapResult.LockBits(new Rectangle(Point.Empty, bitmapResult.Size),
ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
Marshal.Copy(baseImageBuffer, 0, resultImageData.Scan0, baseImageBuffer.Length);
bitmapResult.UnlockBits(resultImageData);
baseImage.UnlockBits(baseImageData);
overlayImage.UnlockBits(overlayImageData);
return bitmapResult;
}
public static Image Interpolate(this Image image1, Image image2, double delta)
{
delta = MathExtensions.Clamp(delta, 0.0, 1.0);
if (image1 is not Bitmap baseImage || image2 is not Bitmap overlayImage ||
image1.Width != image2.Width || image1.Height != image2.Height)
return image1;
BitmapData baseImageData = baseImage.LockBits(new Rectangle(Point.Empty, baseImage.Size),
ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
byte[] baseImageBuffer = new byte[baseImageData.Stride * baseImageData.Height];
Marshal.Copy(baseImageData.Scan0, baseImageBuffer, 0, baseImageBuffer.Length);
baseImage.UnlockBits(baseImageData);
BitmapData overlayImageData = overlayImage.LockBits(new Rectangle(Point.Empty, overlayImage.Size),
ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
byte[] overlayImageBuffer = new byte[overlayImageData.Stride * overlayImageData.Height];
Marshal.Copy(overlayImageData.Scan0, overlayImageBuffer, 0, overlayImageBuffer.Length);
overlayImage.UnlockBits(overlayImageData);
byte[] finalBuffer = new byte[baseImageData.Stride * baseImageData.Height];
for (int k = 0; k < baseImageBuffer.Length && k < overlayImageBuffer.Length; k += 4)
{
finalBuffer[k + 0] = ColorExtensions.Mix(delta, baseImageBuffer[k + 0], overlayImageBuffer[k + 0]);
finalBuffer[k + 1] = ColorExtensions.Mix(delta, baseImageBuffer[k + 1], overlayImageBuffer[k + 1]);
finalBuffer[k + 2] = ColorExtensions.Mix(delta, baseImageBuffer[k + 2], overlayImageBuffer[k + 2]);
finalBuffer[k + 3] = ColorExtensions.Mix(delta, baseImageBuffer[k + 3], overlayImageBuffer[k + 3]);
}
Bitmap bitmapResult = new Bitmap(baseImage.Width, baseImage.Height, PixelFormat.Format32bppArgb);
BitmapData resultImageData = bitmapResult.LockBits(new Rectangle(Point.Empty, bitmapResult.Size),
ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
Marshal.Copy(finalBuffer, 0, resultImageData.Scan0, finalBuffer.Length);
bitmapResult.UnlockBits(resultImageData);
return bitmapResult;
}
}
}

View File

@@ -0,0 +1,8 @@
namespace PckStudio.Core
{
public enum ImageLayoutDirection
{
Horizontal,
Vertical
}
}

View File

@@ -0,0 +1,37 @@
using System.Drawing;
namespace PckStudio.Core.Extensions
{
internal struct ImageSection
{
public readonly Size Size;
public readonly Point Point;
public readonly Rectangle Area;
internal ImageSection(Size originalSize, int index, ImageLayoutDirection layoutDirection)
{
switch(layoutDirection)
{
case ImageLayoutDirection.Horizontal:
{
Size = new Size(originalSize.Height, originalSize.Height);
Point = new Point(index * originalSize.Height, 0);
}
break;
case ImageLayoutDirection.Vertical:
{
Size = new Size(originalSize.Width, originalSize.Width);
Point = new Point(0, index * originalSize.Width);
}
break;
default:
Size = Size.Empty;
Point = new Point(-1, -1);
break;
}
Area = new Rectangle(Point, Size);
}
}
}

View File

@@ -0,0 +1,20 @@
using System.Collections.Generic;
namespace PckStudio.Core.Extensions
{
public static class ListExtensions
{
public static IList<T> Swap<T>(this IList<T> list, int index1, int index2)
{
T temp = list[index1];
list[index1] = list[index2];
list[index2] = temp;
return list;
}
public static bool IndexInRange<T>(this IList<T> list, int index)
{
return index >= 0 && index < list.Count;
}
}
}

View File

@@ -0,0 +1,17 @@
using OMI.Formats.Languages;
namespace PckStudio.Core.Extensions
{
public static class LocFileExtensions
{
public static void InitializeDefault(this LOCFile locFile, string packName) => locFile.Initialize("en-EN", ("IDS_DISPLAY_NAME", packName));
public static void Initialize(this LOCFile locFile, string language, params (string, string)[] locKeyValuePairs)
{
locFile.AddLanguage(language);
foreach ((string, string) locKeyValue in locKeyValuePairs)
locFile.AddLocKey(locKeyValue.Item1, locKeyValue.Item2);
}
}
}

View File

@@ -0,0 +1,37 @@
using OMI.Formats.Material;
namespace PckStudio.Core.Extensions
{
public static class MaterialContainerExtensions
{
private static readonly MaterialContainer.Material[] defaultMaterials = [
new MaterialContainer.Material("bat", "entity_alphatest"),
new MaterialContainer.Material("ender_dragon", "entity_emissive_alpha"),
new MaterialContainer.Material("blaze_head", "entity_emissive_alpha"),
new MaterialContainer.Material("drowned", "entity_emissive_alpha"),
new MaterialContainer.Material("enderman", "entity_emissive_alpha"),
new MaterialContainer.Material("enderman_invisible", "entity_emissive_alpha_only"),
new MaterialContainer.Material("ghast", "entity_emissive_alpha"),
new MaterialContainer.Material("guardian", "entity_alphatest"),
new MaterialContainer.Material("magma_cube", "entity_emissive_alpha"),
new MaterialContainer.Material("zombie_pigman", "entity"),
new MaterialContainer.Material("phantom", "entity_emissive_alpha"),
new MaterialContainer.Material("phantom_invisible", "entity_emissive_alpha_only"),
new MaterialContainer.Material("sheep", "entity_change_color"),
new MaterialContainer.Material("shulker", "entity_change_color"),
new MaterialContainer.Material("skeleton", "entity_alphatest"),
new MaterialContainer.Material("spider", "entity_emissive_alpha"),
new MaterialContainer.Material("spider_invisible", "entity_emissive_alpha_only"),
new MaterialContainer.Material("stray", "entity_alphatest"),
new MaterialContainer.Material("iron_golem", "entity_alphatest"),
new MaterialContainer.Material("wither_boss", "entity_alphatest"),
new MaterialContainer.Material("wither_skeleton", "entity_alphatest"),
new MaterialContainer.Material("wolf", "entity_alphatest_change_color")
];
public static void InitializeDefault(this MaterialContainer materials)
{
materials.AddRange(defaultMaterials);
}
}
}

View File

@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using OMI.Formats.Material;
namespace PckStudio.Core.Extensions
{
public static class MaterialExtensions
{
public static bool HasInvalidEntries(this MaterialContainer materials)
{
return materials.Any(mat => !MaterialContainer.SupportedEntities.Contains(mat.Name) || !MaterialContainer.ValidMaterialTypes.Contains(mat.Type));
}
}
}

View File

@@ -0,0 +1,16 @@
using System;
namespace PckStudio.Core.Extensions
{
public class MathExtensions
{
public static T Clamp<T>(T value, T min, T max) where T : IComparable<T>
{
if (value.CompareTo(min) < 0)
return min;
if (value.CompareTo(max) > 0)
return max;
return value;
}
}
}

View File

@@ -0,0 +1,20 @@
using System;
using OMI.Formats.Model;
using System.Numerics;
namespace PckStudio.Core.Extensions
{
public static class ModelBoxExtension
{
public static BoundingBox GetBoundingBox(this ModelBox modelBox)
{
Vector3 halfSize = modelBox.Size / 2f;
Vector3 halfSizeInflated = new Vector3(modelBox.Inflate) + halfSize;
Vector3 transformedCenter = modelBox.Position + halfSize;
Vector3 start = transformedCenter - halfSizeInflated;
Vector3 end = transformedCenter + halfSizeInflated;
return new BoundingBox(start, end);
}
}
}

View File

@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using OpenTK;
namespace PckStudio.Core.Extensions
{
public static class OpenTKExtensions
{
public static Matrix4 Pivoted(this Matrix4 rotation, Vector3 pivot)
{
var model = Matrix4.CreateTranslation(pivot);
model *= rotation;
model *= Matrix4.CreateTranslation(pivot).Inverted();
return model;
}
public static Vector3 Abs(Vector3 value)
{
return new Vector3(Math.Abs(value.X), Math.Abs(value.Y), Math.Abs(value.Z));
}
}
}

View File

@@ -0,0 +1,226 @@
using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Diagnostics;
using System.Collections.Generic;
using OMI.Formats.Languages;
using OMI.Formats.Pck;
using OMI.Workers;
using PckStudio.Interfaces;
using PckStudio.Core.Deserializer;
using PckStudio.Core.Serializer;
using PckStudio.Core.Skin;
namespace PckStudio.Core.Extensions
{
public static class PckAssetExtensions
{
private const string MipMap = "MipMapLevel";
public static PckAsset CreateNewAssetIf(this PckFile pck, bool condition, string filename, PckAssetType filetype, IDataFormatWriter writer)
{
if (condition)
{
return pck.CreateNewAsset(filename, filetype, writer);
}
return default;
}
public static PckAsset CreateNewAsset(this PckFile pck, string filename, PckAssetType filetype, IDataFormatWriter writer)
{
PckAsset asset = pck.CreateNewAsset(filename, filetype);
asset.SetData(writer);
return asset;
}
public static Image GetTexture(this PckAsset asset)
{
if (asset.Type != PckAssetType.SkinFile &&
asset.Type != PckAssetType.CapeFile &&
asset.Type != PckAssetType.TextureFile)
{
throw new Exception("Asset is not suitable to contain image data.");
}
return asset.GetDeserializedData(ImageDeserializer.DefaultDeserializer);
}
/// <summary>
/// Tries to get the skin id of the skin <paramref name="asset"/>
/// </summary>
/// <param name="asset"></param>
/// <returns>Non-zero base number on success, otherwise 0</returns>
/// <exception cref="InvalidOperationException"></exception>
public static int GetSkinId(this PckAsset asset)
{
if (asset.Type != PckAssetType.SkinFile)
throw new InvalidOperationException("Asset is not a skin.");
const string skinAssetnamePrefix = "dlcskin";
string assetPath = Path.GetFileNameWithoutExtension(asset.Filename);
if (!assetPath.StartsWith(skinAssetnamePrefix))
{
Trace.TraceWarning($"[{nameof(GetSkinId)}] Asset name does not start with '{skinAssetnamePrefix}'");
return 0;
}
int skinId = 0;
if (!int.TryParse(assetPath.Substring(skinAssetnamePrefix.Length), out skinId))
{
Trace.TraceWarning($"[{nameof(GetSkinId)}] Failed to parse Skin Id");
}
return skinId;
}
public static Skin.Skin GetSkin(this PckAsset asset)
{
if (asset.Type != PckAssetType.SkinFile)
throw new InvalidOperationException("Asset is not a skin.");
int skinId = asset.GetSkinId();
string name = asset.GetProperty("DISPLAYNAME");
Image texture = asset.GetTexture();
SkinANIM anim = asset.GetProperty("ANIM", SkinANIM.FromString);
IEnumerable<SkinBOX> boxes = asset.GetMultipleProperties("BOX").Select(kv => SkinBOX.FromString(kv.Value));
IEnumerable<SkinPartOffset> offsets = asset.GetMultipleProperties("OFFSET").Select(kv => SkinPartOffset.FromString(kv.Value));
return new Skin.Skin(name, skinId, texture, anim, boxes, offsets);
}
public static void SetSkin(this PckAsset asset, Skin.Skin skin, LOCFile localizationFile)
{
if (asset.Type != PckAssetType.SkinFile)
throw new InvalidOperationException("Asset is not a skin file");
asset.SetTexture(skin.Texture);
string skinId = skin.Identifier.ToString("d08");
// TODO: keep filepath
asset.Filename = $"dlcskin{skinId}.png";
asset.SetProperty("DISPLAYNAME", skin.MetaData.Name);
if (localizationFile is not null)
{
string skinLocKey = $"IDS_dlcskin{skinId}_DISPLAYNAME";
asset.SetProperty("DISPLAYNAMEID", skinLocKey);
localizationFile.SetLocEntry(skinLocKey, skin.MetaData.Name);
}
if (!string.IsNullOrEmpty(skin.MetaData.Theme))
{
asset.SetProperty("THEMENAME", skin.MetaData.Theme);
if (localizationFile is not null)
{
string skinThemeLocKey = $"IDS_dlcskin{skinId}_THEMENAME";
asset.SetProperty("THEMENAMEID", skinThemeLocKey);
localizationFile.SetLocEntry(skinThemeLocKey, skin.MetaData.Theme);
}
}
if (skin.HasCape)
{
asset.SetProperty("CAPEPATH", $"dlccape{skinId}.png");
}
asset.SetProperty("ANIM", skin.Anim.ToString());
asset.SetProperty("GAME_FLAGS", "0x18");
asset.SetProperty("FREE", "1");
asset.RemoveProperties("BOX");
asset.RemoveProperties("OFFSET");
foreach (SkinBOX box in skin.Model.AdditionalBoxes)
{
asset.AddProperty(box.ToProperty());
}
foreach (SkinPartOffset offset in skin.Model.PartOffsets)
{
asset.AddProperty(offset.ToProperty());
}
}
public static T GetDeserializedData<T>(this PckAsset asset, IPckAssetDeserializer<T> deserializer)
{
return deserializer.Deserialize(asset);
}
public static T GetData<T>(this PckAsset asset, IDataFormatReader<T> formatReader) where T : class
{
using var ms = new MemoryStream(asset.Data);
return formatReader.FromStream(ms);
}
public static void SetSerializedData<T>(this PckAsset asset, T obj, IPckAssetSerializer<T> serializer)
{
serializer.Serialize(obj, ref asset);
}
public static void SetData(this PckAsset asset, IDataFormatWriter formatWriter)
{
using (var stream = new MemoryStream())
{
formatWriter.WriteToStream(stream);
asset.SetData(stream.ToArray());
}
}
public static void SetTexture(this PckAsset asset, Image image)
{
if (asset.Type != PckAssetType.SkinFile &&
asset.Type != PckAssetType.CapeFile &&
asset.Type != PckAssetType.TextureFile)
{
throw new Exception("Asset is not suitable to contain image data.");
}
asset.SetSerializedData(image, ImageSerializer.DefaultSerializer);
}
public static bool IsMipmappedFile(this PckAsset asset)
{
// We only want to test the file name itself. ex: "terrainMipMapLevel2"
string name = Path.GetFileNameWithoutExtension(asset.Filename);
// check if last character is a digit (0-9). If not return false
if (!char.IsDigit(name[name.Length - 1]))
return false;
// If string does not end with MipMapLevel, then it's not MipMapped
if (!name.Remove(name.Length - 1, 1).EndsWith(MipMap))
return false;
return true;
}
public static string GetNormalPath(this PckAsset asset)
{
if (!asset.IsMipmappedFile())
return asset.Filename;
string ext = Path.GetExtension(asset.Filename);
return asset.Filename.Remove(asset.Filename.Length - (MipMap.Length + 1) - ext.Length) + ext;
}
public static void DeserializeProperties(this PckAsset asset, IEnumerable<string> serializedData)
{
IEnumerable<KeyValuePair<string, string>> lines = serializedData
.Select(line => line.Split([' '], 2))
.Where (keyValue => keyValue.Length == 2)
.Select(keyValue => new KeyValuePair<string, string>(keyValue[0].Replace(":", ""), keyValue[1]));
foreach (KeyValuePair<string, string> kv in lines)
{
asset.AddProperty(kv);
}
}
public static IEnumerable<string> SerializeProperties(this PckAsset asset, string seperater = ":")
{
IReadOnlyList<KeyValuePair<string, string>> properties = asset.GetProperties();
return properties.Select(property => property.Key + seperater + property.Value);
}
}
}

View File

@@ -0,0 +1,23 @@
using System;
using System.Reflection;
using System.Windows.Forms;
namespace PckStudio.Core.Extensions
{
public static class PictureBoxExtensions
{
public static bool IsAnimating(this PictureBox pictureBox)
{
FieldInfo fi = typeof(PictureBox).GetField("currentlyAnimating", BindingFlags.NonPublic | BindingFlags.Instance);
return (bool)fi.GetValue(pictureBox);
}
public static void Animate(this PictureBox pictureBox, bool animate)
{
MethodInfo animateMethod = typeof(PictureBox).GetMethod("Animate", BindingFlags.NonPublic | BindingFlags.Instance,
null, new Type[] { typeof(bool) }, null);
animateMethod.Invoke(pictureBox, new object[] { animate });
}
}
}

View File

@@ -0,0 +1,81 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Numerics;
using System.Text;
using System.Threading.Tasks;
using PckStudio.Core.Skin;
namespace PckStudio.Core.Extensions
{
public static class SkinBOXExtensions
{
public static GraphicsPath GetUVGraphicsPath(this SkinBOX skinBOX, Vector2 tillingFactor)
{
var types = new byte[9];
var points = new PointF[9];
var path = new GraphicsPath(FillMode.Winding);
Vector2 uv = skinBOX.UV;
Vector3 size = skinBOX.Size;
path.AddRectangle(new RectangleF(new PointF((uv.X ) * tillingFactor.X, (uv.Y + size.Z) * tillingFactor.Y), new SizeF(size.Z * tillingFactor.X, size.Y * tillingFactor.Y)));
path.AddRectangle(new RectangleF(new PointF((uv.X + size.Z ) * tillingFactor.X, (uv.Y + size.Z) * tillingFactor.Y), new SizeF(size.X * tillingFactor.X, size.Y * tillingFactor.Y)));
path.AddRectangle(new RectangleF(new PointF((uv.X + size.Z + size.X ) * tillingFactor.X, (uv.Y + size.Z) * tillingFactor.Y), new SizeF(size.Z * tillingFactor.X, size.Y * tillingFactor.Y)));
path.AddRectangle(new RectangleF(new PointF((uv.X + size.Z * 2 + size.X) * tillingFactor.X, (uv.Y + size.Z) * tillingFactor.Y), new SizeF(size.X * tillingFactor.X, size.Y * tillingFactor.Y)));
path.AddRectangle(new RectangleF(new PointF((uv.X + size.Z ) * tillingFactor.X, (uv.Y ) * tillingFactor.Y), new SizeF(size.X * tillingFactor.X, size.Z * tillingFactor.Y)));
path.AddRectangle(new RectangleF(new PointF((uv.X + size.Z + size.X ) * tillingFactor.X, (uv.Y ) * tillingFactor.Y), new SizeF(size.X * tillingFactor.X, size.Z * tillingFactor.Y)));
return path;
}
public static GraphicsPath GetUVGraphicsPath(this SkinBOX skinBox)
{
return skinBox.GetUVGraphicsPath(Vector2.One);
}
public static string GetOverlayType(this SkinBOX skinBox)
{
if (!skinBox.IsValidType())
return "";
if (skinBox.IsOverlayPart())
return skinBox.Type;
int index = Array.IndexOf(SkinBOX.BaseTypes, skinBox.Type);
return SkinBOX.OverlayTypes.IndexInRange(index) ? SkinBOX.OverlayTypes[index] : "";
}
public static string GetOverlayType(string type)
{
if (!SkinBOX.IsValidType(type))
return "";
if (SkinBOX.IsOverlayPart(type))
return type;
int index = Array.IndexOf(SkinBOX.BaseTypes, type);
return SkinBOX.OverlayTypes.IndexInRange(index) ? SkinBOX.OverlayTypes[index] : "";
}
public static string GetBaseType(this SkinBOX skinBox)
{
if (!skinBox.IsValidType())
return "";
if (skinBox.IsBasePart())
return skinBox.Type;
int index = Array.IndexOf(SkinBOX.OverlayTypes, skinBox.Type);
return SkinBOX.BaseTypes.IndexInRange(index) ? SkinBOX.BaseTypes[index] : "";
}
public static string GetBaseType(string type)
{
if (!SkinBOX.IsValidType(type))
return "";
if (SkinBOX.IsBasePart(type))
return type;
int index = Array.IndexOf(SkinBOX.OverlayTypes, type);
return SkinBOX.BaseTypes.IndexInRange(index) ? SkinBOX.BaseTypes[index] : "";
}
}
}

View File

@@ -0,0 +1,71 @@
using System;
using System.Collections.Generic;
using System.Drawing.Imaging;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using OMI.Formats.Languages;
using OMI.Formats.Pck;
using PckStudio.Core.Skin;
namespace PckStudio.Core.Extensions
{
public static class SkinExtensions
{
public static PckAsset CreateFile(this Skin.Skin skin, LOCFile localizationFile)
{
string skinId = skin.Identifier.ToString("d08");
PckAsset skinFile = new PckAsset($"dlcskin{skinId}.png", PckAssetType.SkinFile);
skinFile.AddProperty("DISPLAYNAME", skin.MetaData.Name);
if (localizationFile is not null)
{
string skinLocKey = $"IDS_dlcskin{skinId}_DISPLAYNAME";
skinFile.AddProperty("DISPLAYNAMEID", skinLocKey);
localizationFile.AddLocKey(skinLocKey, skin.MetaData.Name);
}
if (!string.IsNullOrEmpty(skin.MetaData.Theme))
{
skinFile.AddProperty("THEMENAME", skin.MetaData.Theme);
if (localizationFile is not null)
{
skinFile.AddProperty("THEMENAMEID", $"IDS_dlcskin{skinId}_THEMENAME");
localizationFile.AddLocKey($"IDS_dlcskin{skinId}_THEMENAME", skin.MetaData.Theme);
}
}
if (skin.HasCape)
{
skinFile.AddProperty("CAPEPATH", $"dlccape{skinId}.png");
}
skinFile.AddProperty("ANIM", skin.Anim);
skinFile.AddProperty("GAME_FLAGS", "0x18");
skinFile.AddProperty("FREE", "1");
foreach (SkinBOX box in skin.Model.AdditionalBoxes)
{
skinFile.AddProperty(box.ToProperty());
}
foreach (SkinPartOffset offset in skin.Model.PartOffsets)
{
skinFile.AddProperty(offset.ToProperty());
}
skinFile.SetTexture(skin.Texture);
return skinFile;
}
public static PckAsset CreateCapeFile(this Skin.Skin skin)
{
if (!skin.HasCape)
throw new InvalidOperationException("Skin does not contain a cape.");
string skinId = skin.Identifier.ToString("d08");
PckAsset capeFile = new PckAsset($"dlccape{skinId}.png", PckAssetType.CapeFile);
capeFile.SetTexture(skin.CapeTexture);
return capeFile;
}
}
}

View File

@@ -0,0 +1,29 @@
namespace PckStudio.Core.Extensions
{
public static class NumericsExtensions
{
//internal static Cube ToCube(this SkinBOX skinBOX) => skinBOX.ToCube(0f);
//internal static Cube ToCube(this SkinBOX skinBOX, float inflate, bool flipZMapping = false)
// => new Cube(skinBOX.Pos.ToOpenTKVector(), skinBOX.Size.ToOpenTKVector(), skinBOX.UV.ToOpenTKVector(), skinBOX.Scale + inflate, skinBOX.Mirror, flipZMapping);
public static OpenTK.Vector3 ToOpenTKVector(this System.Numerics.Vector3 vector3)
{
return new OpenTK.Vector3(vector3.X, vector3.Y, vector3.Z);
}
public static OpenTK.Vector2 ToOpenTKVector(this System.Numerics.Vector2 vector2)
{
return new OpenTK.Vector2(vector2.X, vector2.Y);
}
public static System.Numerics.Vector3 ToNumericsVector(this OpenTK.Vector3 vector3)
{
return new System.Numerics.Vector3(vector3.X, vector3.Y, vector3.Z);
}
public static System.Numerics.Vector2 ToNumericsVector(this OpenTK.Vector2 vector2)
{
return new System.Numerics.Vector2(vector2.X, vector2.Y);
}
}
}

View File

@@ -0,0 +1,47 @@
using System.Collections.Generic;
using System.Windows.Forms;
namespace PckStudio.Core.Extensions
{
public static class TreeNodeExtensions
{
public static bool IsTagOfType<T>(this TreeNode node) where T : class
{
return node?.Tag is T;
}
public static bool TryGetTagData<TOut>(this TreeNode node, out TOut tagData) where TOut : class
{
if (node?.Tag is TOut data)
{
tagData = data;
return true;
}
tagData = default;
return false;
}
public static bool Contains(this TreeNode thisNode, TreeNode childNode)
{
if (childNode.Parent == null)
return false;
if (thisNode.Equals(childNode.Parent))
return true;
// If the parent node is not equal to the first node,
// call the TreeNode.Contains recursively using the parent of the node.
return thisNode.Contains(childNode.Parent);
}
public static List<TreeNode> GetChildNodes(this TreeNode thisNode)
{
List<TreeNode> nodes = new List<TreeNode>(thisNode.Nodes.Count);
foreach (TreeNode node in thisNode.Nodes)
{
nodes.Add(node);
nodes.AddRange(node.GetChildNodes());
}
return nodes;
}
}
}

View File

@@ -0,0 +1,28 @@
using System;
using System.Linq;
using System.Windows.Forms;
namespace PckStudio.Core.Extensions
{
public static class TreeViewExtensions
{
public static TreeNode[] FindPath(this TreeView treeView, string path)
{
if (string.IsNullOrWhiteSpace(path))
return Array.Empty<TreeNode>();
if (!path.Contains(treeView.PathSeparator))
{
return treeView.Nodes.Find(path, false);
}
string segment = path.Substring(0, path.IndexOf(treeView.PathSeparator));
if (treeView.Nodes.ContainsKey(segment))
{
TreeNode[] res = treeView.Nodes[segment].GetChildNodes().Where(node => node.FullPath == path).ToArray();
return res;
}
return Array.Empty<TreeNode>();
}
}
}