diff --git a/Pixi/MyPlugin.cs b/Pixi/MyPlugin.cs
deleted file mode 100644
index 51a7faf..0000000
--- a/Pixi/MyPlugin.cs
+++ /dev/null
@@ -1,63 +0,0 @@
-using System.Reflection;
-
-using IllusionPlugin;
-// using GamecraftModdingAPI;
-using GamecraftModdingAPI.Commands;
-
-namespace Pixi
-{
- public class MyPlugin : IPlugin // the Illusion Plugin Architecture (IPA) will ignore classes that don't implement IPlugin'
- {
- public string Name { get; } = Assembly.GetExecutingAssembly().GetName().Name; // Pixi by default
- // To change the name, change the project's name
-
- public string Version { get; } = Assembly.GetExecutingAssembly().GetName().Version.ToString(); // 0.0.1 by default
- // To change the version, change 0.0.1 in Pixi.csproj
-
- private static readonly string helloWorldCommandName = "HelloWorld"; // command name
-
- // called when Gamecraft shuts down
- public void OnApplicationQuit()
- {
- // Shutdown this mod
- GamecraftModdingAPI.Utility.Logging.LogDebug($"{Name} has shutdown");
-
- // Shutdown the Gamecraft modding API last
- GamecraftModdingAPI.Main.Shutdown();
- }
-
- // called when Gamecraft starts up
- public void OnApplicationStart()
- {
- // Initialize the Gamecraft modding API first
- GamecraftModdingAPI.Main.Init();
- // check out the modding API docs here: https://mod.exmods.org/
-
- // Initialize this mod
- // create SimpleCustomCommandEngine
- // this writes "Hello modding world!" when you execute it in Gamecraft's console
- // (use the forward-slash key '/' to open the console in Gamecraft when in a game)
- SimpleCustomCommandEngine helloWorldCommand = new SimpleCustomCommandEngine(
- () => { GamecraftModdingAPI.Utility.Logging.CommandLog("Hello modding world!"); }, // command action
- // also try using CommandLogWarning or CommandLogError instead of CommandLog
- helloWorldCommandName, // command name (used to invoke it in the console)
- "Says Hello modding world!" // command description (displayed when help command is executed)
- ); // this command can also be executed using the Command Computer
-
- // register the command so the modding API knows about it
- CommandManager.AddCommand(helloWorldCommand);
-
- GamecraftModdingAPI.Utility.Logging.LogDebug($"{Name} has started up");
- }
-
- // unused methods
-
- public void OnFixedUpdate() { } // called once per physics update
-
- public void OnLevelWasInitialized(int level) { } // called after a level is initialized
-
- public void OnLevelWasLoaded(int level) { } // called after a level is loaded
-
- public void OnUpdate() { } // called once per rendered frame (frame update)
- }
-}
\ No newline at end of file
diff --git a/Pixi/PixiPlugin.cs b/Pixi/PixiPlugin.cs
new file mode 100644
index 0000000..899a95f
--- /dev/null
+++ b/Pixi/PixiPlugin.cs
@@ -0,0 +1,289 @@
+using System;
+using System.Reflection;
+using System.IO;
+using System.Runtime.CompilerServices;
+
+using UnityEngine;
+using Unity.Mathematics; // float3
+
+using IllusionPlugin;
+// using GamecraftModdingAPI;
+using GamecraftModdingAPI.Commands;
+using GamecraftModdingAPI.Utility;
+using GamecraftModdingAPI.Blocks;
+
+namespace Pixi
+{
+ public class PixiPlugin : IPlugin // the Illusion Plugin Architecture (IPA) will ignore classes that don't implement IPlugin'
+ {
+ public string Name { get; } = Assembly.GetExecutingAssembly().GetName().Name; // Pixi
+ // To change the name, change the project's name
+
+ public string Version { get; } = Assembly.GetExecutingAssembly().GetName().Version.ToString(); // 0.1.0 (for now)
+ // To change the version, change #.#.# in Pixi.csproj
+
+ private uint width = 32;
+ private uint height = 32;
+
+ private double blockSize = 0.2;
+
+ private PlayerLocationEngine playerLocationEngine = new PlayerLocationEngine();
+
+ // called when Gamecraft shuts down
+ public void OnApplicationQuit()
+ {
+ // Shutdown this mod
+ GamecraftModdingAPI.Utility.Logging.LogDebug($"{Name} has shutdown");
+
+ // Shutdown the Gamecraft modding API last
+ GamecraftModdingAPI.Main.Shutdown();
+ }
+
+ // called when Gamecraft starts up
+ public void OnApplicationStart()
+ {
+ // Initialize the Gamecraft modding API first
+ GamecraftModdingAPI.Main.Init();
+ // check out the modding API docs here: https://mod.exmods.org/
+
+ // Initialize Pixi mod
+ // create SimpleCustomCommandEngine for 2D image importing
+ SimpleCustomCommandEngine pixelate2DCommand = new SimpleCustomCommandEngine(
+ pixelate2DFile, // command action
+ "Pixi2D", // command name (used to invoke it in the console)
+ "Converts an image to blocks.\nLarger images will freeze your game until conversion completes. (Pixi)" // command description (displayed when help command is executed)
+ );
+
+ SimpleCustomCommandEngine pixelate3DCommand = new SimpleCustomCommandEngine(
+ pixelate3DFile, // command action
+ "Pixi3D", // command name (used to invoke it in the console)
+ "Converts a 3D model to blocks.\nLarger models will freeze your game until conversion completes. (Pixi)" // command description (displayed when help command is executed)
+ );
+
+ SimpleCustomCommandEngine scaleCommand = new SimpleCustomCommandEngine(
+ setScale, // command action
+ "PixiScale", // command name (used to invoke it in the console)
+ "Sets the image scale factor for Pixi2D.\nBigger images take longer to convert. (Pixi)" // command description (displayed when help command is executed)
+ );
+
+ // register commands so the modding API knows about it
+ CommandManager.AddCommand(pixelate2DCommand);
+ CommandManager.AddCommand(scaleCommand);
+ GameEngineManager.AddGameEngine(playerLocationEngine);
+
+ GamecraftModdingAPI.Utility.Logging.LogDebug($"{Name} has started up");
+ }
+
+ // unused methods
+
+ public void OnFixedUpdate() { } // called once per physics update
+
+ public void OnLevelWasInitialized(int level) { } // called after a level is initialized
+
+ public void OnLevelWasLoaded(int level) { } // called after a level is loaded
+
+ public void OnUpdate() { } // called once per rendered frame (frame update)
+
+ // pixelation methods
+
+ private void pixelate2DFile(string filepath)
+ {
+ Logging.CommandLogWarning("Large images may freeze your game for a long period");
+ // Load image file and convert to Gamecraft blocks
+ Texture2D img = new Texture2D((int)width, (int)height);
+ // load file into texture
+ try
+ {
+ byte[] imgData = File.ReadAllBytes(filepath);
+ img.LoadImage(imgData);
+ }
+ catch (Exception e)
+ {
+ Logging.CommandLogError($"Failed to load picture data. Reason: {e.Message}");
+ Logging.LogException(e);
+ return;
+ }
+ float3 position = playerLocationEngine.GetPlayerLocation(0u);
+ uint blockCount = 0;
+ position.x += 1f;
+ //position.y += 1f;
+ float zero_y = position.y;
+ // 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)
+ for (int x = 0; x < width; x++)
+ {
+ QuantizedPixel qVoxel = new QuantizedPixel{color = BlockColors.Default, darkness = 10};
+ float3 scale = new float3(1, 1, 1);
+ position.x += (float)(blockSize);
+ for (int y = 0; y < height; y++)
+ {
+ //position.y += (float)blockSize;
+ Color pixel = img.GetPixel(x, y);
+ QuantizedPixel qPixel = quantizeColor(pixel);
+ if (qPixel.darkness != qVoxel.darkness || qPixel.color != qVoxel.color || qPixel.visible != qVoxel.visible)
+ {
+ if (y != 0)
+ {
+ if (qVoxel.visible)
+ {
+ position.y = zero_y + (float)((y * blockSize + (y - scale.y) * blockSize) / 2);
+ Placement.PlaceBlock(BlockIDs.AluminiumCube, position, color: qVoxel.color, darkness: qVoxel.darkness, scale: scale);
+ blockCount++;
+ }
+ scale = new float3(1, 1, 1);
+ }
+ qVoxel = qPixel;
+ }
+ else
+ {
+ scale.y += 1;
+ }
+
+ }
+ if (qVoxel.visible)
+ {
+ position.y = zero_y + (float)((height * blockSize + (height - scale.y) * blockSize) / 2);
+ Placement.PlaceBlock(BlockIDs.AluminiumCube, position, color: qVoxel.color, darkness: qVoxel.darkness, scale: scale);
+ blockCount++;
+ }
+ //position.y = zero_y;
+ }
+ Logging.CommandLog($"Placed {width}x{height} image beside you ({blockCount} blocks total)");
+ Logging.MetaLog($"Saved {(width * height) - blockCount} blocks while placing {filepath}");
+ }
+
+ private void setScale(uint _width, uint _height)
+ {
+ width = _width;
+ height = _height;
+ Logging.CommandLog($"Pixi image size set to {width}x{height}");
+ }
+
+ private void pixelate3DFile(string filepath)
+ {
+ // TODO?
+ Logging.CommandLogError("Oh no you found this command!\nCommand functionality not implemented (yet)");
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private QuantizedPixel quantizeColor(Color pixel)
+ {
+ BlockColors color = BlockColors.Default;
+ int darkness = 0;
+ Logging.MetaDebugLog($"Color (r:{pixel.r}, g:{pixel.g}, b:{pixel.b})");
+ 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.5 && (pixel.r - pixel.b) > pixel.r * 0.5)
+ {
+ // Red is much higher than other pixels
+ darkness = (int)(10 - (pixel.r * 11));
+ color = BlockColors.Red;
+ }
+ else if ((pixel.g - pixel.b) > pixel.g * 0.3)
+ {
+ // Green is much higher than blue
+ darkness = (int)(10 - ((pixel.r + pixel.g) * 5.1));
+ if ((pixel.r - pixel.g) > pixel.r * 0.5)
+ {
+ color = BlockColors.Orange;
+ }
+ else
+ {
+ color = BlockColors.Yellow;
+ }
+
+ }
+ else if ((pixel.b - pixel.g) > pixel.b * 0.5)
+ {
+ // Blue is much higher than green
+ darkness = (int)(10 - ((pixel.r + pixel.b) * 4.9));
+ color = BlockColors.Purple;
+ }
+ else
+ {
+ // Green is close strength to blue
+ darkness = (int)(10 - ((pixel.r + pixel.g + pixel.b) * 4.8));
+ color = BlockColors.Pink;
+ }
+ }
+ else if (pixel.g >= pixel.r && pixel.g >= pixel.b)
+ {
+ // Green is highest
+ if ((pixel.g - pixel.r) > pixel.g * 0.5 && (pixel.g - pixel.b) > pixel.g * 0.5)
+ {
+ // Green is much higher than other pixels
+ darkness = (int)(10 - (pixel.g * 11));
+ color = BlockColors.Green;
+ }
+ else if ((pixel.r - pixel.b) > pixel.r * 0.5)
+ {
+ // 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.5)
+ {
+ // Blue is much higher than red
+ darkness = (int)(10 - ((pixel.g + pixel.b) * 5.1));
+ color = BlockColors.Aqua;
+ }
+ else
+ {
+ // Red is close strength to blue
+ darkness = (int)(10 - ((pixel.r + pixel.g + pixel.b) * 4.8));
+ color = BlockColors.Lime;
+ }
+ }
+ else if (pixel.b >= pixel.g && pixel.b >= pixel.r)
+ {
+ // Blue is highest
+ if ((pixel.b - pixel.g) > pixel.b * 0.5 && (pixel.b - pixel.r) > pixel.b * 0.5)
+ {
+ // Blue is much higher than other pixels
+ darkness = (int)(10 - (pixel.b * 11));
+ color = BlockColors.Blue;
+ }
+ else if ((pixel.g - pixel.r) > pixel.g * 0.5)
+ {
+ // Green is much higher than red
+ darkness = (int)(10 - ((pixel.g + pixel.b) * 5.1));
+ color = BlockColors.Aqua;
+ }
+ else if ((pixel.r - pixel.g) > pixel.r * 0.5)
+ {
+ // Red is much higher than green
+ darkness = (int)(10 - ((pixel.r + pixel.b) * 4.9));
+ color = BlockColors.Purple;
+ }
+ else
+ {
+ // Green is close strength to red
+ darkness = (int)(10 - ((pixel.r + pixel.g + pixel.b) * 4.9));
+ color = BlockColors.Aqua;
+ }
+ }
+ if (darkness > 9) darkness = 9;
+ if (darkness < 0) darkness = 0;
+ // darkness 0 is the most saturated (it's not just the lightest)
+ Logging.MetaDebugLog($"Quantized Color {color} d:{darkness}");
+ return new QuantizedPixel { color = color, darkness = (byte)darkness, visible = pixel.a > 0.5f};
+ }
+ }
+
+ internal struct QuantizedPixel
+ {
+ public BlockColors color;
+
+ public byte darkness;
+
+ public bool visible;
+ }
+}
\ No newline at end of file
diff --git a/Pixi/PlayerLocationEngine.cs b/Pixi/PlayerLocationEngine.cs
new file mode 100644
index 0000000..a13681c
--- /dev/null
+++ b/Pixi/PlayerLocationEngine.cs
@@ -0,0 +1,26 @@
+using System;
+
+using GamecraftModdingAPI.Utility;
+using Svelto.ECS;
+using Unity.Mathematics;
+using RobocraftX.Physics;
+using RobocraftX.Character;
+
+namespace Pixi
+{
+ internal class PlayerLocationEngine : IApiEngine
+ {
+ public string Name => "PixiPlayerLocationGameEngine";
+
+ public EntitiesDB entitiesDB { set; private get; }
+
+ public void Dispose() {}
+
+ public void Ready() {}
+
+ public float3 GetPlayerLocation(uint playerId)
+ {
+ return entitiesDB.QueryEntity(playerId, CharacterExclusiveGroups.OnFootGroup).position;
+ }
+ }
+}