Browse Source

Add blueprint RC cube functionality (rods-only atm)

tags/v0.4.0-beta2
NGnius (Graham) 4 years ago
parent
commit
cdd474e1ec
11 changed files with 492 additions and 131 deletions
  1. +16
    -0
      Pixi/Common/BlockJsonInfo.cs
  2. +219
    -0
      Pixi/Common/ColorSpaceUtility.cs
  3. +75
    -0
      Pixi/Common/VoxelObjectNotationUtility.cs
  4. +12
    -2
      Pixi/Images/ImageCommands.cs
  5. +4
    -112
      Pixi/Images/PixelUtility.cs
  6. +6
    -1
      Pixi/Pixi.csproj
  7. +4
    -0
      Pixi/PixiPlugin.cs
  8. +3
    -1
      Pixi/Robots/CubeInfo.cs
  9. +68
    -2
      Pixi/Robots/CubeUtility.cs
  10. +59
    -13
      Pixi/Robots/RobotCommands.cs
  11. +26
    -0
      Pixi/blueprints.json

+ 16
- 0
Pixi/Common/BlockJsonInfo.cs View File

@@ -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;
}
}

+ 219
- 0
Pixi/Common/ColorSpaceUtility.cs View File

@@ -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 };
}
}
}

+ 75
- 0
Pixi/Common/VoxelObjectNotationUtility.cs View File

@@ -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;
}
}
}
}

+ 12
- 2
Pixi/Images/ImageCommands.cs View File

@@ -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");
}
}
}

+ 4
- 112
Pixi/Images/PixelUtility.cs View File

@@ -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;
}


+ 6
- 1
Pixi/Pixi.csproj View File

@@ -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>

+ 4
- 0
Pixi/PixiPlugin.cs View File

@@ -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");
}


+ 3
- 1
Pixi/Robots/CubeInfo.cs View File

@@ -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;
}
}

+ 68
- 2
Pixi/Robots/CubeUtility.cs View File

@@ -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());
}
}
}

+ 59
- 13
Pixi/Robots/RobotCommands.cs View File

@@ -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);
}
}
}

+ 26
- 0
Pixi/blueprints.json
File diff suppressed because it is too large
View File