@@ -0,0 +1,16 @@ | |||
using System; | |||
namespace Pixi.Common | |||
{ | |||
public struct BlockJsonInfo | |||
{ | |||
public string name; | |||
public float[] position; | |||
public float[] rotation; | |||
public float[] color; | |||
public float[] scale; | |||
} | |||
} |
@@ -0,0 +1,219 @@ | |||
using System; | |||
using System.Linq; | |||
using System.Collections.Generic; | |||
using System.Runtime.CompilerServices; | |||
using UnityEngine; | |||
using GamecraftModdingAPI.Blocks; | |||
using GamecraftModdingAPI.Utility; | |||
namespace Pixi.Common | |||
{ | |||
public static class ColorSpaceUtility | |||
{ | |||
private const float optimal_delta = 0.2f; | |||
private static Dictionary<BlockColor, float[]> colorMap = null; | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static BlockColor QuantizeToBlockColor(Color pixel) | |||
{ | |||
if (colorMap == null) BuildColorMap(); | |||
float[] closest = new float[3] { 1, 1, 1 }; | |||
BlockColor c = new BlockColor | |||
{ | |||
Color = BlockColors.Default, | |||
Darkness = 0, | |||
}; | |||
BlockColor[] keys = colorMap.Keys.ToArray(); | |||
for (int k = 0; k < keys.Length; k++) | |||
{ | |||
float[] color = colorMap[keys[k]]; | |||
float[] distance = new float[3] { Math.Abs(pixel.r - color[0]), Math.Abs(pixel.g - color[1]), Math.Abs(pixel.b - color[2]) }; | |||
if ((distance[0] + distance[1] + distance[2]) < (closest[0] + closest[1] + closest[2])) | |||
{ | |||
c = keys[k]; | |||
closest = distance; | |||
if ((closest[0] + closest[1] + closest[2]) < optimal_delta) | |||
{ | |||
return c; | |||
} | |||
} | |||
} | |||
#if DEBUG | |||
Logging.MetaLog($"Final delta ({closest[0]},{closest[1]},{closest[2]}) t:{closest[0] + closest[1] + closest[2]}"); | |||
#endif | |||
return c; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static BlockColor QuantizeToBlockColor(float[] pixel) | |||
{ | |||
return QuantizeToBlockColor(new Color(pixel[0], pixel[1], pixel[2])); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static float[] UnquantizeToArray(BlockColor c) | |||
{ | |||
if (colorMap == null) BuildColorMap(); | |||
return colorMap[c]; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static float[] UnquantizeToArray(BlockColors color, byte darkness = 0) | |||
{ | |||
return UnquantizeToArray(new BlockColor | |||
{ | |||
Color = color, | |||
Darkness = darkness, | |||
}); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static Color UnquantizeToColor(BlockColor c) | |||
{ | |||
float[] t = UnquantizeToArray(c); | |||
return new Color(t[0], t[1], t[2]); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static Color UnquantizeToColor(BlockColors color, byte darkness = 0) | |||
{ | |||
return UnquantizeToColor(new BlockColor | |||
{ | |||
Color = color, | |||
Darkness = darkness, | |||
}); | |||
} | |||
private static void BuildColorMap() | |||
{ | |||
colorMap = new Dictionary<BlockColor, float[]>(); | |||
// TODO create actual color map | |||
foreach (BlockColors c in Enum.GetValues(typeof(BlockColors))) | |||
{ | |||
for (byte d = 0; d < 10; d++) | |||
{ | |||
BlockColor colorStruct = new BlockColor | |||
{ | |||
Color = c, | |||
Darkness = d, | |||
}; | |||
colorMap[colorStruct] = new float[3] { 1f, 0f, 1f }; | |||
} | |||
} | |||
// this was done manually -- never again | |||
// White | |||
colorMap[new BlockColor { Color = BlockColors.White, Darkness = 0 }] = new float[3] { 1f, 1f, 1f}; | |||
colorMap[new BlockColor { Color = BlockColors.White, Darkness = 1 }] = new float[3] { 0.88f, 0.98f, 0.99f }; | |||
colorMap[new BlockColor { Color = BlockColors.White, Darkness = 2 }] = new float[3] { 0.80f, 0.89f, 0.99f }; | |||
colorMap[new BlockColor { Color = BlockColors.White, Darkness = 3 }] = new float[3] { 0.746f, 0.827f, 0.946f }; | |||
colorMap[new BlockColor { Color = BlockColors.White, Darkness = 4 }] = new float[3] { 0.71f, 0.789f, 0.888f }; | |||
colorMap[new BlockColor { Color = BlockColors.White, Darkness = 5 }] = new float[3] { 0.597f, 0.664f, 0.742f }; | |||
colorMap[new BlockColor { Color = BlockColors.White, Darkness = 6 }] = new float[3] { 0.484f, 0.535f, 0.61f }; | |||
colorMap[new BlockColor { Color = BlockColors.White, Darkness = 7 }] = new float[3] { 0.355f, 0.39f, 0.449f }; | |||
colorMap[new BlockColor { Color = BlockColors.White, Darkness = 8 }] = new float[3] { 0f, 0f, 0f }; | |||
colorMap[new BlockColor { Color = BlockColors.White, Darkness = 9 }] = new float[3] { 0.581f, 0.643f, 0.745f }; | |||
// Pink | |||
colorMap[new BlockColor { Color = BlockColors.Pink, Darkness = 0 }] = new float[3] { 1f, 0.657f, 1f }; | |||
colorMap[new BlockColor { Color = BlockColors.Pink, Darkness = 1 }] = new float[3] { 0.912f, 0.98f, 0.993f }; | |||
colorMap[new BlockColor { Color = BlockColors.Pink, Darkness = 2 }] = new float[3] { 0.897f, 0.905f, 0.991f }; | |||
colorMap[new BlockColor { Color = BlockColors.Pink, Darkness = 3 }] = new float[3] { 0.892f, 0.776f, 0.988f }; | |||
colorMap[new BlockColor { Color = BlockColors.Pink, Darkness = 4 }] = new float[3] { 0.898f, 0.698f, 0.992f }; | |||
colorMap[new BlockColor { Color = BlockColors.Pink, Darkness = 5 }] = new float[3] { 0.875f, 0.267f, 0.882f }; | |||
colorMap[new BlockColor { Color = BlockColors.Pink, Darkness = 6 }] = new float[3] { 0.768f, 0.199f, 0.767f }; | |||
colorMap[new BlockColor { Color = BlockColors.Pink, Darkness = 7 }] = new float[3] { 0.628f, 0.15f, 0.637f }; | |||
colorMap[new BlockColor { Color = BlockColors.Pink, Darkness = 8 }] = new float[3] { 0.435f, 0.133f, 0.439f }; | |||
colorMap[new BlockColor { Color = BlockColors.Pink, Darkness = 9 }] = new float[3] { 0.726f, 0.659f, 0.871f }; | |||
// Purple | |||
colorMap[new BlockColor { Color = BlockColors.Purple, Darkness = 0 }] = new float[3] { 0.764f, 0.587f, 1f }; | |||
colorMap[new BlockColor { Color = BlockColors.Purple, Darkness = 1 }] = new float[3] { 0.893f, 0.966f, 0.992f }; | |||
colorMap[new BlockColor { Color = BlockColors.Purple, Darkness = 2 }] = new float[3] { 0.842f, 0.877f, 0.991f }; | |||
colorMap[new BlockColor { Color = BlockColors.Purple, Darkness = 3 }] = new float[3] { 0.794f, 0.747f, 0.99f }; | |||
colorMap[new BlockColor { Color = BlockColors.Purple, Darkness = 4 }] = new float[3] { 0.783f, 0.669f, 0.992f }; | |||
colorMap[new BlockColor { Color = BlockColors.Purple, Darkness = 5 }] = new float[3] { 0.636f, 0.249f, 0.991f }; | |||
colorMap[new BlockColor { Color = BlockColors.Purple, Darkness = 6 }] = new float[3] { 0.548f, 0.18f, 0.896f }; | |||
colorMap[new BlockColor { Color = BlockColors.Purple, Darkness = 7 }] = new float[3] { 0.441f, 0.152f, 0.726f }; | |||
colorMap[new BlockColor { Color = BlockColors.Purple, Darkness = 8 }] = new float[3] { 0.308f, 0.135f, 0.498f }; | |||
colorMap[new BlockColor { Color = BlockColors.Purple, Darkness = 9 }] = new float[3] { 0.659f, 0.646f, 0.909f }; | |||
// Blue | |||
colorMap[new BlockColor { Color = BlockColors.Blue, Darkness = 0 }] = new float[3] { 0.449f, 0.762f, 1f }; | |||
colorMap[new BlockColor { Color = BlockColors.Blue, Darkness = 1 }] = new float[3] { 0.856f, 0.971f, 0.992f }; | |||
colorMap[new BlockColor { Color = BlockColors.Blue, Darkness = 2 }] = new float[3] { 0.767f, 0.907f, 0.989f }; | |||
colorMap[new BlockColor { Color = BlockColors.Blue, Darkness = 3 }] = new float[3] { 0.642f, 0.836f, 0.992f }; | |||
colorMap[new BlockColor { Color = BlockColors.Blue, Darkness = 4 }] = new float[3] { 0.564f, 0.812f, 0.989f }; | |||
colorMap[new BlockColor { Color = BlockColors.Blue, Darkness = 5 }] = new float[3] { 0.211f, 0.621f, 0.989f }; | |||
colorMap[new BlockColor { Color = BlockColors.Blue, Darkness = 6 }] = new float[3] { 0.143f, 0.525f, 0.882f }; | |||
colorMap[new BlockColor { Color = BlockColors.Blue, Darkness = 7 }] = new float[3] { 0.114f, 0.410f, 0.705f }; | |||
colorMap[new BlockColor { Color = BlockColors.Blue, Darkness = 8 }] = new float[3] { 0.116f, 0.289f, 0.481f }; | |||
colorMap[new BlockColor { Color = BlockColors.Blue, Darkness = 9 }] = new float[3] { 0.571f, 0.701f, 0.901f }; | |||
// Aqua | |||
colorMap[new BlockColor { Color = BlockColors.Aqua, Darkness = 0 }] = new float[3] { 0.408f, 0.963f, 1f }; | |||
colorMap[new BlockColor { Color = BlockColors.Aqua, Darkness = 1 }] = new float[3] { 0.838f, 0.976f, 0.990f }; | |||
colorMap[new BlockColor { Color = BlockColors.Aqua, Darkness = 2 }] = new float[3] { 0.747f, 0.961f, 0.994f }; | |||
colorMap[new BlockColor { Color = BlockColors.Aqua, Darkness = 3 }] = new float[3] { 0.605f, 0.948f, 0.990f }; | |||
colorMap[new BlockColor { Color = BlockColors.Aqua, Darkness = 4 }] = new float[3] { 0.534f, 0.954f, 0.993f }; | |||
colorMap[new BlockColor { Color = BlockColors.Aqua, Darkness = 5 }] = new float[3] { 0.179f, 0.841f, 0.991f }; | |||
colorMap[new BlockColor { Color = BlockColors.Aqua, Darkness = 6 }] = new float[3] { 0.121f, 0.719f, 0.868f }; | |||
colorMap[new BlockColor { Color = BlockColors.Aqua, Darkness = 7 }] = new float[3] { 0.117f, 0.574f, 0.687f }; | |||
colorMap[new BlockColor { Color = BlockColors.Aqua, Darkness = 8 }] = new float[3] { 0.116f, 0.399f, 0.478f }; | |||
colorMap[new BlockColor { Color = BlockColors.Aqua, Darkness = 9 }] = new float[3] { 0.556f, 0.768f, 0.901f }; | |||
// Green | |||
colorMap[new BlockColor { Color = BlockColors.Green, Darkness = 0 }] = new float[3] { 0.344f, 1f, 0.579f }; | |||
colorMap[new BlockColor { Color = BlockColors.Green, Darkness = 1 }] = new float[3] { 0.823f, 0.977f, 0.994f }; | |||
colorMap[new BlockColor { Color = BlockColors.Green, Darkness = 2 }] = new float[3] { 0.731f, 0.966f, 0.958f }; | |||
colorMap[new BlockColor { Color = BlockColors.Green, Darkness = 3 }] = new float[3] { 0.643f, 0.964f, 0.873f }; | |||
colorMap[new BlockColor { Color = BlockColors.Green, Darkness = 4 }] = new float[3] { 0.498f, 0.961f, 0.721f }; | |||
colorMap[new BlockColor { Color = BlockColors.Green, Darkness = 5 }] = new float[3] { 0.176f, 0.853f, 0.415f }; | |||
colorMap[new BlockColor { Color = BlockColors.Green, Darkness = 6 }] = new float[3] { 0.120f, 0.728f, 0.350f }; | |||
colorMap[new BlockColor { Color = BlockColors.Green, Darkness = 7 }] = new float[3] { 0.105f, 0.560f, 0.264f }; | |||
colorMap[new BlockColor { Color = BlockColors.Green, Darkness = 8 }] = new float[3] { 0.122f, 0.392f, 0.221f }; | |||
colorMap[new BlockColor { Color = BlockColors.Green, Darkness = 9 }] = new float[3] { 0.542f, 0.771f, 0.717f }; | |||
// Lime | |||
colorMap[new BlockColor { Color = BlockColors.Lime, Darkness = 0 }] = new float[3] { 0.705f, 1f, 0.443f }; | |||
colorMap[new BlockColor { Color = BlockColors.Lime, Darkness = 1 }] = new float[3] { 0.869f, 0.978f, 0.991f }; | |||
colorMap[new BlockColor { Color = BlockColors.Lime, Darkness = 2 }] = new float[3] { 0.815f, 0.967f, 0.932f }; | |||
colorMap[new BlockColor { Color = BlockColors.Lime, Darkness = 3 }] = new float[3] { 0.778f, 0.962f, 0.821f }; | |||
colorMap[new BlockColor { Color = BlockColors.Lime, Darkness = 4 }] = new float[3] { 0.753f, 0.964f, 0.631f }; | |||
colorMap[new BlockColor { Color = BlockColors.Lime, Darkness = 5 }] = new float[3] { 0.599f, 0.855f, 0.268f }; | |||
colorMap[new BlockColor { Color = BlockColors.Lime, Darkness = 6 }] = new float[3] { 0.505f, 0.712f, 0.201f }; | |||
colorMap[new BlockColor { Color = BlockColors.Lime, Darkness = 7 }] = new float[3] { 0.376f, 0.545f, 0.185f }; | |||
colorMap[new BlockColor { Color = BlockColors.Lime, Darkness = 8 }] = new float[3] { 0.268f, 0.379f, 0.172f }; | |||
colorMap[new BlockColor { Color = BlockColors.Lime, Darkness = 9 }] = new float[3] { 0.631f, 0.768f, 0.690f }; | |||
// Yellow | |||
colorMap[new BlockColor { Color = BlockColors.Yellow, Darkness = 0 }] = new float[3] { 0.893f, 1f, 0.457f }; | |||
colorMap[new BlockColor { Color = BlockColors.Yellow, Darkness = 1 }] = new float[3] { 0.887f, 0.981f, 0.995f }; | |||
colorMap[new BlockColor { Color = BlockColors.Yellow, Darkness = 2 }] = new float[3] { 0.878f, 0.971f, 0.920f }; | |||
colorMap[new BlockColor { Color = BlockColors.Yellow, Darkness = 3 }] = new float[3] { 0.874f, 0.964f, 0.802f }; | |||
colorMap[new BlockColor { Color = BlockColors.Yellow, Darkness = 4 }] = new float[3] { 0.875f, 0.964f, 0.619f }; | |||
colorMap[new BlockColor { Color = BlockColors.Yellow, Darkness = 5 }] = new float[3] { 0.771f, 0.846f, 0.246f }; | |||
colorMap[new BlockColor { Color = BlockColors.Yellow, Darkness = 6 }] = new float[3] { 0.638f, 0.703f, 0.192f }; | |||
colorMap[new BlockColor { Color = BlockColors.Yellow, Darkness = 7 }] = new float[3] { 0.477f, 0.522f, 0.142f }; | |||
colorMap[new BlockColor { Color = BlockColors.Yellow, Darkness = 8 }] = new float[3] { 0.330f, 0.363f, 0.151f }; | |||
colorMap[new BlockColor { Color = BlockColors.Yellow, Darkness = 9 }] = new float[3] { 0.693f, 0.763f, 0.678f }; | |||
// Orange | |||
colorMap[new BlockColor { Color = BlockColors.Orange, Darkness = 0 }] = new float[3] { 0.891f, 0.750f, 0.423f }; | |||
colorMap[new BlockColor { Color = BlockColors.Orange, Darkness = 1 }] = new float[3] { 0.883f, 0.948f, 0.992f }; | |||
colorMap[new BlockColor { Color = BlockColors.Orange, Darkness = 2 }] = new float[3] { 0.877f, 0.873f, 0.894f }; | |||
colorMap[new BlockColor { Color = BlockColors.Orange, Darkness = 3 }] = new float[3] { 0.878f, 0.831f, 0.771f }; | |||
colorMap[new BlockColor { Color = BlockColors.Orange, Darkness = 4 }] = new float[3] { 0.886f, 0.801f, 0.595f }; | |||
colorMap[new BlockColor { Color = BlockColors.Orange, Darkness = 5 }] = new float[3] { 0.777f, 0.621f, 0.241f }; | |||
colorMap[new BlockColor { Color = BlockColors.Orange, Darkness = 6 }] = new float[3] { 0.637f, 0.507f, 0.168f }; | |||
colorMap[new BlockColor { Color = BlockColors.Orange, Darkness = 7 }] = new float[3] { 0.466f, 0.364f, 0.123f }; | |||
colorMap[new BlockColor { Color = BlockColors.Orange, Darkness = 8 }] = new float[3] { 0.323f, 0.266f, 0.138f }; | |||
colorMap[new BlockColor { Color = BlockColors.Orange, Darkness = 9 }] = new float[3] { 0.689f, 0.672f, 0.667f }; | |||
// Red | |||
colorMap[new BlockColor { Color = BlockColors.Red, Darkness = 0 }] = new float[3] { 0.890f, 0.323f, 0.359f }; | |||
colorMap[new BlockColor { Color = BlockColors.Red, Darkness = 1 }] = new float[3] { 0.879f, 0.863f, 0.987f }; | |||
colorMap[new BlockColor { Color = BlockColors.Red, Darkness = 2 }] = new float[3] { 0.872f, 0.758f, 0.868f }; | |||
colorMap[new BlockColor { Color = BlockColors.Red, Darkness = 3 }] = new float[3] { 0.887f, 0.663f, 0.756f }; | |||
colorMap[new BlockColor { Color = BlockColors.Red, Darkness = 4 }] = new float[3] { 0.903f, 0.546f, 0.608f }; | |||
colorMap[new BlockColor { Color = BlockColors.Red, Darkness = 5 }] = new float[3] { 0.785f, 0.222f, 0.222f }; | |||
colorMap[new BlockColor { Color = BlockColors.Red, Darkness = 6 }] = new float[3] { 0.641f, 0.155f, 0.152f }; | |||
colorMap[new BlockColor { Color = BlockColors.Red, Darkness = 7 }] = new float[3] { 0.455f, 0.105f, 0.108f }; | |||
colorMap[new BlockColor { Color = BlockColors.Red, Darkness = 8 }] = new float[3] { 0.320f, 0.121f, 0.133f }; | |||
colorMap[new BlockColor { Color = BlockColors.Red, Darkness = 9 }] = new float[3] { 0.687f, 0.571f, 0.661f }; | |||
} | |||
} | |||
} |
@@ -0,0 +1,75 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Text; | |||
using Newtonsoft.Json; | |||
using GamecraftModdingAPI; | |||
using GamecraftModdingAPI.Blocks; | |||
namespace Pixi.Common | |||
{ | |||
public static class VoxelObjectNotationUtility | |||
{ | |||
private static readonly float[] origin_base = new float[3] { 0, 0, 0 }; | |||
private static Dictionary<string, BlockIDs> enumMap = null; | |||
public static string SerializeBlocks(Block[] blocks, float[] origin = null) | |||
{ | |||
BlockJsonInfo[] blockJsons = new BlockJsonInfo[blocks.Length]; | |||
for (int i = 0; i < blocks.Length; i++) | |||
{ | |||
blockJsons[i] = JsonObject(blocks[i], origin); | |||
} | |||
return JsonConvert.SerializeObject(blockJsons); | |||
} | |||
public static byte[] SerializeBlocksToBytes(Block[] blocks) | |||
{ | |||
return Encoding.UTF8.GetBytes(SerializeBlocks(blocks)); | |||
} | |||
public static BlockJsonInfo[] DeserializeBlocks(byte[] data) | |||
{ | |||
return DeserializeBlocks(Encoding.UTF8.GetString(data)); | |||
} | |||
public static BlockJsonInfo[] DeserializeBlocks(string data) | |||
{ | |||
return JsonConvert.DeserializeObject<BlockJsonInfo[]>(data); | |||
} | |||
public static BlockJsonInfo JsonObject(Block block, float[] origin = null) | |||
{ | |||
if (origin == null) origin = origin_base; | |||
return new BlockJsonInfo | |||
{ | |||
name = block.Type.ToString(), | |||
position = new float[3] { block.Position.x - origin[0], block.Position.y - origin[1], block.Position.z - origin[2]}, | |||
rotation = new float[3] { block.Rotation.x, block.Rotation.y, block.Rotation.z }, | |||
color = ColorSpaceUtility.UnquantizeToArray(block.Color), | |||
scale = new float[3] {block.Scale.x, block.Scale.y, block.Scale.z}, | |||
}; | |||
} | |||
public static BlockIDs NameToEnum(BlockJsonInfo block) | |||
{ | |||
return NameToEnum(block.name); | |||
} | |||
public static BlockIDs NameToEnum(string name) | |||
{ | |||
if (enumMap == null) GenerateEnumMap(); | |||
return enumMap[name]; | |||
} | |||
private static void GenerateEnumMap() | |||
{ | |||
enumMap = new Dictionary<string, BlockIDs>(); | |||
foreach(BlockIDs e in Enum.GetValues(typeof(BlockIDs))) | |||
{ | |||
enumMap[e.ToString()] = e; | |||
} | |||
} | |||
} | |||
} |
@@ -1,4 +1,5 @@ | |||
using System; | |||
using System.Diagnostics; | |||
using System.IO; | |||
using System.Text; | |||
using System.Security.Cryptography; | |||
@@ -92,6 +93,7 @@ namespace Pixi.Images | |||
position.x += 1f; | |||
position.y += (float)blockSize; | |||
float zero_y = position.y; | |||
Stopwatch timer = Stopwatch.StartNew(); | |||
// convert the image to blocks | |||
// this groups same-colored pixels in the same column into a single block to reduce the block count | |||
// any further pixel-grouping optimisations (eg 2D grouping) risk increasing conversion time higher than O(x*y) | |||
@@ -142,8 +144,9 @@ namespace Pixi.Images | |||
} | |||
//position.y = zero_y; | |||
} | |||
Logging.CommandLog($"Placed {img.width}x{img.height} image beside you ({blockCount} blocks total)"); | |||
Logging.MetaLog($"Saved {(img.width * img.height) - blockCount} blocks ({blockCount / (img.width * img.height)}%) while placing {filepath}"); | |||
timer.Stop(); | |||
Logging.CommandLog($"Placed {img.width}x{img.height} image beside you ({blockCount} blocks total, {blockCount * 100 / (img.width * img.height)}%)"); | |||
Logging.MetaLog($"Placed {blockCount} in {timer.ElapsedMilliseconds}ms (saved {(img.width * img.height) - blockCount} blocks -- {blockCount * 100 / (img.width * img.height)}% original size) for {filepath}"); | |||
} | |||
public static void Pixelate2DFileToTextBlock(string filepath) | |||
@@ -166,6 +169,7 @@ namespace Pixi.Images | |||
float3 position = new Player(PlayerType.Local).Position; | |||
position.x += 1f; | |||
position.y += (float)blockSize; | |||
Stopwatch timer = Stopwatch.StartNew(); | |||
string text = PixelUtility.TextureToString(img); | |||
TextBlock textBlock = TextBlock.PlaceNew(position, scale: new float3(Mathf.Ceil(img.width / 16), 1, Mathf.Ceil(img.height / 16))); | |||
textBlock.Text = text; | |||
@@ -179,6 +183,9 @@ namespace Pixi.Images | |||
textId += textHash[i].ToString("X2"); | |||
} | |||
textBlock.TextBlockId = textId; | |||
timer.Stop(); | |||
Logging.CommandLog($"Placed {img.width}x{img.height} image in text block named {textId} beside you ({text.Length} characters)"); | |||
Logging.MetaLog($"Completed image text block {textId} synthesis in {timer.ElapsedMilliseconds}ms containing {text.Length} characters for {img.width*img.height} pixels"); | |||
} | |||
public static void Pixelate2DFileToCommand(string filepath, string textBlockId) | |||
@@ -201,6 +208,7 @@ namespace Pixi.Images | |||
float3 position = new Player(PlayerType.Local).Position; | |||
position.x += 1f; | |||
position.y += (float)blockSize; | |||
Stopwatch timer = Stopwatch.StartNew(); | |||
float zero_y = position.y; | |||
string text = PixelUtility.TextureToString(img); // conversion | |||
ConsoleBlock console = ConsoleBlock.PlaceNew(position); | |||
@@ -209,6 +217,8 @@ namespace Pixi.Images | |||
console.Arg1 = "\"" + textBlockId + "\""; | |||
console.Arg2 = "\"" + text + "\""; | |||
console.Arg3 = ""; | |||
Logging.CommandLog($"Placed {img.width}x{img.height} image in console block beside you ({text.Length} characters)"); | |||
Logging.MetaLog($"Completed image console block {textBlockId} synthesis in {timer.ElapsedMilliseconds}ms containing {text.Length} characters for {img.width * img.height} pixels"); | |||
} | |||
} | |||
} |
@@ -16,128 +16,20 @@ namespace Pixi.Images | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static BlockInfo QuantizePixel(Color pixel) | |||
{ | |||
BlockColors color = BlockColors.Default; | |||
int darkness = 0; | |||
bool force = false; | |||
#if DEBUG | |||
Logging.MetaLog($"Color (r:{pixel.r}, g:{pixel.g}, b:{pixel.b})"); | |||
#endif | |||
if (Mathf.Abs(pixel.r - pixel.g) <= pixel.r * 0.1f && Mathf.Abs(pixel.r - pixel.b) <= pixel.r * 0.1f) | |||
{ | |||
color = BlockColors.White; | |||
darkness = (int)(10 - ((pixel.r + pixel.g + pixel.b) * 3.5)); | |||
//Logging.MetaDebugLog($"Color (r:{pixel.r}, g:{pixel.g}, b:{pixel.b})"); | |||
} | |||
else if (pixel.r >= pixel.g && pixel.r >= pixel.b) | |||
{ | |||
// Red is highest | |||
if ((pixel.r - pixel.g) > pixel.r * 0.65 && (pixel.r - pixel.b) > pixel.r * 0.55) | |||
{ | |||
// Red is much higher than other pixels | |||
darkness = (int)(9 - (pixel.r * 8.01)); | |||
color = BlockColors.Red; | |||
} | |||
else if ((pixel.g - pixel.b) > pixel.g * 0.25) | |||
{ | |||
// Green is much higher than blue | |||
if ((pixel.r - pixel.g) < pixel.r * 0.8) | |||
{ | |||
darkness = (int)(10 - ((pixel.r * 2.1 + pixel.g) * 2.1)); | |||
color = BlockColors.Orange; | |||
} | |||
else | |||
{ | |||
darkness = (int)(10 - ((pixel.r * 2.1 + pixel.g) * 2.2)); | |||
color = BlockColors.Yellow; | |||
} | |||
} | |||
else if ((pixel.b - pixel.g) > pixel.b * 0.3) | |||
{ | |||
// Blue is much higher than green | |||
darkness = (int)(10 - ((pixel.r + pixel.b) * 5.0)); | |||
color = BlockColors.Purple; | |||
} | |||
else | |||
{ | |||
// Green is close strength to blue | |||
darkness = (int)(10 - ((pixel.r * 2.1 + pixel.g + pixel.b) * 2.5)); | |||
color = darkness < 6 ? BlockColors.Pink : BlockColors.Orange; | |||
force = true; | |||
} | |||
} | |||
else if (pixel.g >= pixel.r && pixel.g >= pixel.b) | |||
{ | |||
// Green is highest | |||
if ((pixel.g - pixel.r) > pixel.g * 0.6 && (pixel.g - pixel.b) > pixel.g * 0.48) | |||
{ | |||
// Green is much higher than other pixels | |||
darkness = (int)(10 - (pixel.g * 10.1)); | |||
color = BlockColors.Green; | |||
} | |||
else if ((pixel.r - pixel.b) > pixel.r * 0.3) | |||
{ | |||
// Red is much higher than blue | |||
darkness = (int)(10 - ((pixel.r + pixel.g) * 5.1)); | |||
color = BlockColors.Yellow; | |||
} | |||
else if ((pixel.b - pixel.r) > pixel.b * 0.2) | |||
{ | |||
// Blue is much higher than red | |||
darkness = (int)(9 - ((pixel.g + pixel.b) * 5.1)); | |||
color = BlockColors.Aqua; | |||
} | |||
else | |||
{ | |||
// Red is close strength to blue | |||
darkness = (int)(10 - ((pixel.r + pixel.g * 2.2 + pixel.b) * 2.9)); | |||
color = BlockColors.Lime; | |||
} | |||
} | |||
else if (pixel.b >= pixel.g && pixel.b >= pixel.r) | |||
{ | |||
// Blue is highest | |||
if ((pixel.b - pixel.g) > pixel.b * 0.6 && (pixel.b - pixel.r) > pixel.b * 0.6) | |||
{ | |||
// Blue is much higher than other pixels | |||
darkness = (int)(10 - (pixel.b * 10.1)); | |||
color = BlockColors.Blue; | |||
} | |||
else if ((pixel.g - pixel.r) > pixel.g * 0.3) | |||
{ | |||
// Green is much higher than red | |||
darkness = (int)(10 - ((pixel.g + pixel.b) * 5.1)); | |||
if (darkness == 4 || darkness == 5) darkness = 0; | |||
else if (darkness < 3) darkness = 4; | |||
color = BlockColors.Aqua; | |||
} | |||
else if ((pixel.r - pixel.g) > pixel.r * 0.3) | |||
{ | |||
// Red is much higher than green | |||
darkness = (int)(10 - ((pixel.r + pixel.b) * 5.0)); | |||
color = BlockColors.Purple; | |||
} | |||
else | |||
{ | |||
// Green is close strength to red | |||
darkness = (int)(10 - ((pixel.r + pixel.g + pixel.b * 2.2) * 3.0)); | |||
color = BlockColors.Aqua; | |||
} | |||
} | |||
// level 9 is not darker than lvl 8 | |||
if (darkness > 8 && !force) darkness = 8; | |||
// darkness 0 is the most saturated (it's not just the lightest) | |||
if (darkness < 0) darkness = 0; | |||
BlockColor c = ColorSpaceUtility.QuantizeToBlockColor(pixel); | |||
BlockInfo result = new BlockInfo | |||
{ | |||
block = pixel.a > 0.75 ? BlockIDs.AluminiumCube : BlockIDs.GlassCube, | |||
color = color, | |||
darkness = (byte)darkness, | |||
color = c.Color, | |||
darkness = c.Darkness, | |||
visible = pixel.a > 0.5f, | |||
}; | |||
#if DEBUG | |||
Logging.MetaLog($"Quantized {color} (b:{result.block} d:{result.darkness} v:{result.visible})"); | |||
Logging.MetaLog($"Quantized {result.color} (b:{result.block} d:{result.darkness} v:{result.visible})"); | |||
#endif | |||
return result; | |||
} | |||
@@ -3,7 +3,7 @@ | |||
<PropertyGroup> | |||
<TargetFramework>net472</TargetFramework> | |||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects> | |||
<Version>0.3.0</Version> | |||
<Version>0.4.0</Version> | |||
<Authors>NGnius</Authors> | |||
<PackageLicenseExpression>MIT</PackageLicenseExpression> | |||
<PackageProjectUrl>https://git.exmods.org/NGnius/Pixi</PackageProjectUrl> | |||
@@ -807,12 +807,17 @@ | |||
<Reference Include="GamecraftModdingAPI"> | |||
<HintPath>..\..\ref\Plugins\GamecraftModdingAPI.dll</HintPath> | |||
</Reference> | |||
<Reference Include="GamecraftModdingAPI"> | |||
<HintPath>..\..\ref\Plugins\GamecraftModdingAPI.dll</HintPath> | |||
</Reference> | |||
</ItemGroup> | |||
<!--End Dependencies--> | |||
<ItemGroup> | |||
<None Remove="cubes-id.json" /> | |||
<None Remove="blueprints.json" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<EmbeddedResource Include="cubes-id.json" /> | |||
<EmbeddedResource Include="blueprints.json" /> | |||
</ItemGroup> | |||
</Project> |
@@ -48,6 +48,10 @@ namespace Pixi | |||
// Robot functionality | |||
RobotCommands.CreateRobotCRFCommand(); | |||
RobotCommands.CreateRobotFileCommand(); | |||
#if DEBUG | |||
// Development functionality | |||
RobotCommands.CreatePartDumpCommand(); | |||
#endif | |||
Logging.LogDebug($"{Name} has started up"); | |||
} | |||
@@ -6,7 +6,7 @@ namespace Pixi.Robots | |||
{ | |||
public struct CubeInfo | |||
{ | |||
// so you can't inherit from structs in C#... | |||
// you can't inherit from structs in C#... | |||
// this is an extension of BlockInfo | |||
public BlockIDs block; | |||
@@ -24,5 +24,7 @@ namespace Pixi.Robots | |||
public float3 scale; | |||
public string name; | |||
public uint cubeId; | |||
} | |||
} |
@@ -7,9 +7,13 @@ using System.Runtime.CompilerServices; | |||
using RobocraftX.Common; | |||
using Newtonsoft.Json; | |||
using Unity.Mathematics; | |||
using UnityEngine; | |||
using GamecraftModdingAPI.Blocks; | |||
using GamecraftModdingAPI.Utility; | |||
using GamecraftModdingAPI; | |||
using Pixi.Common; | |||
namespace Pixi.Robots | |||
{ | |||
@@ -17,6 +21,8 @@ namespace Pixi.Robots | |||
{ | |||
private static Dictionary<uint, string> map = null; | |||
private static Dictionary<uint, BlockJsonInfo[]> blueprintMap = null; | |||
public static RobotStruct? ParseRobotInfo(string robotInfo) | |||
{ | |||
try | |||
@@ -69,7 +75,7 @@ namespace Pixi.Robots | |||
public static CubeInfo TranslateSpacialEnumerations(uint cubeId, byte x, byte y, byte z, byte rotation, byte colour, byte colour_x, byte colour_y, byte colour_z) | |||
{ | |||
if (x != colour_x || z != colour_z || y != colour_y) return default; | |||
CubeInfo result = new CubeInfo { visible = true }; | |||
CubeInfo result = new CubeInfo { visible = true, cubeId = cubeId }; | |||
TranslateBlockColour(colour, ref result); | |||
TranslateBlockPosition(x, y, z, ref result); | |||
TranslateBlockRotation(rotation, ref result); | |||
@@ -80,7 +86,7 @@ namespace Pixi.Robots | |||
return result; | |||
} | |||
//[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
private static void TranslateBlockRotation(byte rotation, ref CubeInfo result) | |||
{ | |||
// face refers to the face of the block connected to the bottom of the current one | |||
@@ -414,5 +420,65 @@ namespace Pixi.Robots | |||
result.name = cubeName; | |||
} | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static Block[] BuildBlueprintOrTextBlock(CubeInfo cube, float3 actualPosition, int scale = 3) | |||
{ | |||
// actualPosition is the middle of the cube | |||
if (blueprintMap == null) LoadBlueprintMap(); | |||
if (!blueprintMap.ContainsKey(cube.cubeId) || scale != 3) | |||
{ | |||
#if DEBUG | |||
Logging.LogWarning($"Missing blueprint for {cube.name} (id:{cube.cubeId}), substituting {cube.block}"); | |||
#endif | |||
return new Block[] { Block.PlaceNew(cube.block, actualPosition, cube.rotation, cube.color, cube.darkness, scale: cube.scale) }; | |||
} | |||
#if DEBUG | |||
Logging.MetaLog($"Found blueprint for {cube.name} (id:{cube.cubeId})"); | |||
#endif | |||
Quaternion cubeQuaternion = Quaternion.Euler(cube.rotation); | |||
BlockJsonInfo[] blueprint = blueprintMap[cube.cubeId]; | |||
float3 correctionVec = new float3((float)(0), (float)(RobotCommands.blockSize), (float)(0)); | |||
Block[] placedBlocks = new Block[blueprint.Length]; | |||
for (int i = 0; i < blueprint.Length; i++) | |||
{ | |||
BlockColor blueprintBlockColor = ColorSpaceUtility.QuantizeToBlockColor(blueprint[i].color); | |||
BlockColors blockColor = blueprintBlockColor.Color == BlockColors.White && blueprintBlockColor.Darkness == 0 ? cube.color : blueprintBlockColor.Color; | |||
byte blockDarkness = blueprintBlockColor.Color == BlockColors.White && blueprintBlockColor.Darkness == 0 ? cube.darkness : blueprintBlockColor.Darkness; | |||
float3 bluePos = new float3(blueprint[i].position[0], blueprint[i].position[1], blueprint[i].position[2]); | |||
float3 blueScale = new float3(blueprint[i].scale[0], blueprint[i].scale[1], blueprint[i].scale[2]); | |||
float3 blueRot = new float3(blueprint[i].rotation[0], blueprint[i].rotation[1], blueprint[i].rotation[2]); | |||
float3 physicalLocation = (float3)(cubeQuaternion * bluePos) + actualPosition;// + (blueprintSizeRotated / 2); | |||
//physicalLocation.x += blueprintSize.x / 2; | |||
physicalLocation -= (float3)(cubeQuaternion * correctionVec); | |||
//physicalLocation.y -= (float)(RobotCommands.blockSize * scale / 2); | |||
//float3 physicalScale = (float3)(cubeQuaternion * blueScale); // this actually over-rotates when combined with rotation | |||
float3 physicalScale = blueScale; | |||
float3 physicalRotation = (cubeQuaternion * Quaternion.Euler(blueRot)).eulerAngles; | |||
#if DEBUG | |||
Logging.MetaLog($"Placing blueprint block at {physicalLocation} rot{physicalRotation} scale{physicalScale}"); | |||
Logging.MetaLog($"Location math check original:{bluePos} rotated: {(float3)(cubeQuaternion * bluePos)} actualPos: {actualPosition} result: {physicalLocation}"); | |||
Logging.MetaLog($"Scale math check original:{blueScale} rotation: {(float3)cubeQuaternion.eulerAngles} result: {physicalScale}"); | |||
Logging.MetaLog($"Rotation math check original:{blueRot} rotated: {(cubeQuaternion * Quaternion.Euler(blueRot))} result: {physicalRotation}"); | |||
#endif | |||
placedBlocks[i] = Block.PlaceNew(VoxelObjectNotationUtility.NameToEnum(blueprint[i].name), | |||
physicalLocation, | |||
physicalRotation, | |||
blockColor, | |||
blockDarkness, | |||
scale: physicalScale); | |||
} | |||
#if DEBUG | |||
Logging.MetaLog($"Placed {placedBlocks.Length} blocks for blueprint {cube.name} (id:{cube.cubeId})"); | |||
#endif | |||
return placedBlocks; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
private static void LoadBlueprintMap() | |||
{ | |||
StreamReader bluemap = new StreamReader(Assembly.GetExecutingAssembly().GetManifestResourceStream("Pixi.blueprints.json")); | |||
blueprintMap = JsonConvert.DeserializeObject<Dictionary<uint, BlockJsonInfo[]>>(bluemap.ReadToEnd()); | |||
} | |||
} | |||
} |
@@ -12,11 +12,15 @@ using GamecraftModdingAPI.Commands; | |||
using GamecraftModdingAPI.Players; | |||
using GamecraftModdingAPI.Utility; | |||
using Pixi.Common; | |||
namespace Pixi.Robots | |||
{ | |||
public static class RobotCommands | |||
{ | |||
private static double blockSize = 0.2; | |||
internal const double blockSize = 0.2; | |||
public static int CubeSize = 3; | |||
public static void CreateRobotFileCommand() | |||
{ | |||
@@ -36,6 +40,15 @@ namespace Pixi.Robots | |||
.Build(); | |||
} | |||
public static void CreatePartDumpCommand() | |||
{ | |||
CommandBuilder.Builder() | |||
.Name("DumpVON") | |||
.Description("Dump a block structure to a JSON file compatible with Pixi's internal VON format") | |||
.Action<string>(DumpBlockStructure) | |||
.Build(); | |||
} | |||
private static void ImportRobotFile(string filepath) | |||
{ | |||
string file; | |||
@@ -58,20 +71,33 @@ namespace Pixi.Robots | |||
float3 position = new Player(PlayerType.Local).Position; | |||
position.y += (float)blockSize; | |||
CubeInfo[] cubes = CubeUtility.ParseCubes(robot.Value); | |||
Block[] blocks = new Block[cubes.Length]; | |||
Block[][] blocks = new Block[cubes.Length][]; | |||
for (int c = 0; c < cubes.Length; c++) // sometimes I wish this were C++ | |||
{ | |||
CubeInfo cube = cubes[c]; | |||
float3 realPosition = (cube.position * (float)blockSize) + position; | |||
blocks[c] = Block.PlaceNew(cube.block, realPosition, cube.rotation, cube.color, cube.darkness, scale: cube.scale); | |||
float3 realPosition = (cube.position * (float)blockSize * CubeSize) + position; | |||
if (cube.block == BlockIDs.TextBlock && !string.IsNullOrEmpty(cube.name)) | |||
{ | |||
// TextBlock block ID means it's a placeholder | |||
blocks[c] = CubeUtility.BuildBlueprintOrTextBlock(cube, realPosition, CubeSize); | |||
} | |||
else | |||
{ | |||
blocks[c] = new Block[] { Block.PlaceNew(cube.block, realPosition, cube.rotation, cube.color, cube.darkness, CubeSize) }; | |||
} | |||
} | |||
// build placeholders | |||
// Note: this is a separate loop because everytime a new block is placed, | |||
// a slow Sync() call is required to access it's properties. | |||
// This way, one Sync() call is needed, instead of O(cubes.Length) calls | |||
for (int c = 0; c < cubes.Length; c++) | |||
{ | |||
CubeInfo cube = cubes[c]; | |||
// the goal is for this to never evaluate to true (ie all cubes are translated correctly) | |||
if (!string.IsNullOrEmpty(cube.name) && cube.block == BlockIDs.TextBlock) | |||
if (!string.IsNullOrEmpty(cube.name) && cube.block == BlockIDs.TextBlock && blocks[c].Length == 1) | |||
{ | |||
blocks[c].Specialise<TextBlock>().Text = cube.name; | |||
//Logging.MetaLog($"Block is {blocks[c][0].Type} and was placed as {cube.block}"); | |||
blocks[c][0].Specialise<TextBlock>().Text = cube.name; | |||
} | |||
} | |||
Logging.CommandLog($"Placed {robot.Value.name} by {robot.Value.addedByDisplayName} ({cubes.Length} cubes) beside you"); | |||
@@ -102,23 +128,43 @@ namespace Pixi.Robots | |||
float3 position = new Player(PlayerType.Local).Position; | |||
position.y += (float)blockSize; | |||
CubeInfo[] cubes = CubeUtility.ParseCubes(robot); | |||
Block[] blocks = new Block[cubes.Length]; | |||
Block[][] blocks = new Block[cubes.Length][]; | |||
for (int c = 0; c < cubes.Length; c++) // sometimes I wish this were C++ | |||
{ | |||
CubeInfo cube = cubes[c]; | |||
float3 realPosition = (cube.position * (float)blockSize) + position; | |||
blocks[c] = Block.PlaceNew(cube.block, realPosition, cube.rotation, cube.color, cube.darkness, scale: cube.scale); | |||
} | |||
CubeInfo cube = cubes[c]; | |||
float3 realPosition = (cube.position * (float)blockSize * CubeSize) + position; | |||
if (cube.block == BlockIDs.TextBlock && !string.IsNullOrEmpty(cube.name)) | |||
{ | |||
// TextBlock block ID means it's a placeholder | |||
blocks[c] = CubeUtility.BuildBlueprintOrTextBlock(cube, realPosition, CubeSize); | |||
} | |||
else | |||
{ | |||
blocks[c] = new Block[] { Block.PlaceNew(cube.block, realPosition, cube.rotation, cube.color, cube.darkness, CubeSize) }; | |||
} | |||
} | |||
for (int c = 0; c < cubes.Length; c++) | |||
{ | |||
CubeInfo cube = cubes[c]; | |||
// the goal is for this to never evaluate to true (ie all cubes are translated correctly) | |||
if (!string.IsNullOrEmpty(cube.name) && cube.block == BlockIDs.TextBlock) | |||
if (!string.IsNullOrEmpty(cube.name) && cube.block == BlockIDs.TextBlock && blocks[c].Length == 1) | |||
{ | |||
blocks[c].Specialise<TextBlock>().Text = cube.name; | |||
//Logging.MetaLog($"Block is {blocks[c][0].Type} and was placed as {cube.block}"); | |||
blocks[c][0].Specialise<TextBlock>().Text = cube.name; | |||
} | |||
} | |||
Logging.CommandLog($"Placed {robot.name} by {robot.addedByDisplayName} ({cubes.Length} cubes) beside you"); | |||
} | |||
private static void DumpBlockStructure(string filename) | |||
{ | |||
Player local = new Player(PlayerType.Local); | |||
Block baseBlock = local.GetBlockLookedAt(); | |||
Block[] blocks = baseBlock.GetConnectedCubes(); | |||
if (blocks.Length == 0) return; | |||
float3 basePos = baseBlock.Position; | |||
string von = VoxelObjectNotationUtility.SerializeBlocks(blocks, new float[] { basePos.x, basePos.y, basePos.z }); | |||
File.WriteAllText(filename, von); | |||
} | |||
} | |||
} |