diff --git a/Algorithms/GridShaper.cs b/Algorithms/GridShaper.cs index 1757988..0d0bb1e 100644 --- a/Algorithms/GridShaper.cs +++ b/Algorithms/GridShaper.cs @@ -1,15 +1,43 @@ -using Assimp; -using g4; +using g4; +using ILGPU; +using ILGPU.Runtime; +using PropertyTools.DataAnnotations; using SpaceEditor.Rocks; -using System; -using System.Collections.Generic; using System.IO; -using System.Linq; +using System.Runtime; using System.Text; -using PropertyTools.DataAnnotations; namespace SpaceEditor.Algorithms; +// Unmanaged, SIMD-free representation of a 3D vector for ILGPU +public struct Float3 +{ + public float X, Y, Z; + public Float3(float x, float y, float z) { X = x; Y = y; Z = z; } +} + +// Unmanaged representation of a triangle for the GPU +public struct GpuTriangle +{ + public Float3 V0; + public Float3 V1; + public Float3 V2; + public Float3 MinBounds; + public Float3 MaxBounds; +} + +// Struct to bypass the C# 16-parameter limit for delegates +public struct GpuSlopeParams +{ + public int GridX, GridY, GridZ; + public int PAx, PAy, PAz; + public int PBx, PBy, PBz; + public int SAx, SAy, SAz; + public int SBx, SBy, SBz; + public int Content; + public int MustBeSupported; +} + public class GridShaper { public DMesh3 Mesh { get; } @@ -21,10 +49,53 @@ public GridShaper(DMesh3 mesh, DMeshAABBTree3 tree) this.Tree = tree; } + // Class to cache the GPU context and kernels globally + public static class GpuSetup + { + public static Context Context { get; private set; } + public static Accelerator Accelerator { get; private set; } + public static Action, ArrayView, Float3, float, int, int, int> VoxelizeKernel { get; private set; } + public static Action, int> InitGridKernel { get; private set; } + public static Action, GpuSlopeParams> SlopeKernel { get; private set; } + + public static readonly object SyncLock = new object(); + + static GpuSetup() + { + RebuildContext(); + } + + public static void RebuildContext() + { + lock (SyncLock) + { + // Force ILGPU to release all cached VRAM pools back to the OS + try { Accelerator?.Dispose(); } catch { } + try { Context?.Dispose(); } catch { } + + Context = Context.CreateDefault(); + Accelerator = Context.GetPreferredDevice(preferCPU: false).CreateAccelerator(Context); + System.Diagnostics.Debug.WriteLine($"\n[ILGPU INITIALIZATION] Compiled and Cached on: {Accelerator.Name} (Type: {Accelerator.AcceleratorType})\n"); + + VoxelizeKernel = Accelerator.LoadAutoGroupedStreamKernel< + Index1D, ArrayView, ArrayView, Float3, float, int, int, int>( + VoxelizationKernel.Voxelize); + + InitGridKernel = Accelerator.LoadAutoGroupedStreamKernel< + Index1D, ArrayView, int>( + VoxelizationKernel.InitializeGrid); + + SlopeKernel = Accelerator.LoadAutoGroupedStreamKernel< + Index1D, ArrayView, GpuSlopeParams>( + VoxelizationKernel.GenerateSlopes); + } + } + } + public static class BlockSizes { public const string TwoPointFive = "2.5m"; - public const string HalfMeter = "0.5m (VERY VERY slow on large ships!)"; + public const string HalfMeter = "0.5m (May be slow on large ships!)"; public const string TwentyFiveC = nameof(TwentyFiveC); } @@ -32,7 +103,7 @@ public class GeneratorSettings { public bool SlopesUpper { get; set; } = true; public bool SlopesLower { get; set; } = true; - public bool SlopesSides { get; set; } = true; + public bool SlopesSides { get; set; } = true; public bool SlopesMustBeSupported { get; set; } = false; [ItemsSourceProperty(nameof(BlockSizeValues))] @@ -47,412 +118,729 @@ public class GeneratorSettings ]; } - public BlueprintMesh Generate(GeneratorSettings settings, CancellationToken ct) + // --- PUBLIC FACADES --- + // These methods are the entry points for generating a blueprint mesh, either using GPU acceleration or CPU processing. + public BlueprintMesh Generate(GeneratorSettings settings, CancellationToken ct, IProgress<(double, string)> progress = null) + => CoreGenerate(settings, useGpu: true, ct, progress); + + public BlueprintMesh GenerateCpu(GeneratorSettings settings, CancellationToken ct, IProgress<(double, string)> progress = null) + => CoreGenerate(settings, useGpu: false, ct, progress); + + + // --- UNIFIED GENERATOR --- + private BlueprintMesh CoreGenerate(GeneratorSettings settings, bool useGpu, CancellationToken ct, IProgress<(double, string)> progress) { + // ========================================== + // PHASE 1: SHARED SETUP & GRID ALLOCATION + // ========================================== var blockSize = settings.BlockSize switch { BlockSizes.TwoPointFive => ShapeDB.LargeBlockSize, BlockSizes.HalfMeter => ShapeDB.MidBlockSize }; - var minimalBounds = this.Tree.Bounds; - minimalBounds.Min -= blockSize; - minimalBounds.Max += blockSize; - - var boundingBox = new AxisAlignedBox3d(new Vector3d(0), blockSize / 2); - while (boundingBox.Contains(minimalBounds) == false) - { - //TODO: Fix block offset - boundingBox.Scale(2, 2, 2); - } + var bounds = this.Tree.Bounds; + bounds.Expand(blockSize); + + int gridX = (int)Math.Ceiling(bounds.Width / blockSize); + int gridY = (int)Math.Ceiling(bounds.Height / blockSize); + int gridZ = (int)Math.Ceiling(bounds.Depth / blockSize); - var cellCount = (int) Math.Ceiling(boundingBox.MaxDim / blockSize); - var indexer = new ShiftGridIndexer3(boundingBox.Min, blockSize); + long totalGridVolume = (long)gridX * gridY * gridZ; - var bmp = new DenseGrid3i(cellCount, cellCount, cellCount, BlueprintMesh.NoContent); - - var blueprint = new BlueprintMesh(); - blueprint.Blocks = bmp; - blueprint.Coords = indexer; - blueprint.Shapes = settings.BlockSize switch + var indexer = new ShiftGridIndexer3(bounds.Min, blockSize); + var bmp = new DenseGrid3i(gridX, gridY, gridZ, BlueprintMesh.NoContent); + int triangleCount = this.Mesh.TriangleCount; + + var blueprint = new BlueprintMesh { - BlockSizes.TwoPointFive => ShapeDB.LargeShapes, - BlockSizes.HalfMeter => ShapeDB.MidShapes + Blocks = bmp, + Coords = indexer, + Shapes = settings.BlockSize switch + { + BlockSizes.TwoPointFive => ShapeDB.LargeShapes, + BlockSizes.HalfMeter => ShapeDB.MidShapes + } }; - - foreach (var triangle in this.Mesh.EnumerateTriangles()) + + IEnumerable activeBlocks; + + // ========================================== + // PHASE 2 & 3: GPU VOXELIZATION & SLOPE GENERATION + // ========================================== + if (useGpu) { - var triBox = triangle.ToBox(); - foreach (var cell in Enumerators.BoxRange(triBox, indexer)) + Accelerator accelerator; + Action, ArrayView, Float3, float, int, int, int> voxelizeKernel; + Action, int> initGridKernel; + Action, GpuSlopeParams> slopeKernel; + + lock (GpuSetup.SyncLock) { - var cellBox = indexer.ToBox(cell); - if (cellBox.IntersectWithTriangle(triangle) != IntersectResult.NoIntersection) + accelerator = GpuSetup.Accelerator; + voxelizeKernel = GpuSetup.VoxelizeKernel; + initGridKernel = GpuSetup.InitGridKernel; + slopeKernel = GpuSetup.SlopeKernel; + } + + System.Diagnostics.Debug.WriteLine($"\n[ILGPU VERIFICATION] Executing on: {accelerator.Name} (Type: {accelerator.AcceleratorType})\n"); + + var flatTriangles = new GpuTriangle[triangleCount]; + int tIndex = 0; + + foreach (var triangle in this.Mesh.EnumerateTriangles()) + { + ct.ThrowIfCancellationRequested(); + if (tIndex % 5000 == 0) progress?.Report((0.1 * ((double)tIndex / triangleCount), "Mesh Flattening...")); + + var box = triangle.ToBox(); + flatTriangles[tIndex++] = new GpuTriangle { - bmp[cell] = 0; //Cube - } + V0 = new Float3((float)triangle.V0.x, (float)triangle.V0.y, (float)triangle.V0.z), + V1 = new Float3((float)triangle.V1.x, (float)triangle.V1.y, (float)triangle.V1.z), + V2 = new Float3((float)triangle.V2.x, (float)triangle.V2.y, (float)triangle.V2.z), + MinBounds = new Float3((float)box.Min.x, (float)box.Min.y, (float)box.Min.z), + MaxBounds = new Float3((float)box.Max.x, (float)box.Max.y, (float)box.Max.z) + }; } - } - if (settings.SlopesUpper) - { - ExecSlopes(1); - ExecSlopes(2); - ExecSlopes(3); - ExecSlopes(4); - } + int totalCells = (int)totalGridVolume; - if (settings.SlopesLower) - { - ExecSlopes(5); - ExecSlopes(6); - ExecSlopes(7); - ExecSlopes(8); - } + try + { + using var deviceTriangles = accelerator.Allocate1D(flatTriangles); + using var deviceGrid = accelerator.Allocate1D(totalCells); - if (settings.SlopesSides) - { - ExecSlopes(9); - ExecSlopes(10); - ExecSlopes(11); - ExecSlopes(12); - } + initGridKernel(totalCells, deviceGrid.View, BlueprintMesh.NoContent); + + Float3 origin = new Float3((float)bounds.Min.x, (float)bounds.Min.y, (float)bounds.Min.z); + + voxelizeKernel(deviceTriangles.IntExtent, deviceTriangles.View, deviceGrid.View, origin, blockSize, gridX, gridY, gridZ); + accelerator.Synchronize(); + progress?.Report((0.40, "Voxelization complete on GPU...")); + + int totalSlopePasses = (settings.SlopesUpper ? 4 : 0) + (settings.SlopesLower ? 4 : 0) + (settings.SlopesSides ? 4 : 0); + int executedPasses = 0; + + void ExecGpuSlopes(int content) + { + var shapeInfo = blueprint.Shapes[content]; + var probeDirectionA = -Base6Directions.Vectors[shapeInfo.Forward]; + var probeDirectionB = Base6Directions.Vectors[shapeInfo.Up]; + var supportDirectionA = -probeDirectionA; + var supportDirectionB = -probeDirectionB; + + var slopeParams = new GpuSlopeParams + { + GridX = gridX, + GridY = gridY, + GridZ = gridZ, + PAx = probeDirectionA.x, + PAy = probeDirectionA.y, + PAz = probeDirectionA.z, + PBx = probeDirectionB.x, + PBy = probeDirectionB.y, + PBz = probeDirectionB.z, + SAx = supportDirectionA.x, + SAy = supportDirectionA.y, + SAz = supportDirectionA.z, + SBx = supportDirectionB.x, + SBy = supportDirectionB.y, + SBz = supportDirectionB.z, + Content = content, + MustBeSupported = settings.SlopesMustBeSupported ? 1 : 0 + }; + + slopeKernel(totalCells, deviceGrid.View, slopeParams); + accelerator.Synchronize(); + + if (totalSlopePasses > 0) + { + int currentPass = Interlocked.Increment(ref executedPasses); + progress?.Report((0.40 + (0.30 * ((double)currentPass / totalSlopePasses)), "GPU Slope Evaluation...")); + } + } + + if (settings.SlopesUpper) { ExecGpuSlopes(1); ExecGpuSlopes(2); ExecGpuSlopes(3); ExecGpuSlopes(4); } + if (settings.SlopesLower) { ExecGpuSlopes(5); ExecGpuSlopes(6); ExecGpuSlopes(7); ExecGpuSlopes(8); } + if (settings.SlopesSides) { ExecGpuSlopes(9); ExecGpuSlopes(10); ExecGpuSlopes(11); ExecGpuSlopes(12); } + + int[] flatResults = deviceGrid.GetAsArray1D(); + _ = Task.Run(() => GpuSetup.RebuildContext()); + + int processedZ = 0; + var gpuActiveBlocks = new System.Collections.Concurrent.ConcurrentBag(); + + Parallel.For(0, gridZ, new ParallelOptions { CancellationToken = ct }, z => + { + for (int y = 0; y < gridY; y++) + { + for (int x = 0; x < gridX; x++) + { + int flatIdx = x + (y * gridX) + (z * gridX * gridY); + if (flatResults[flatIdx] != BlueprintMesh.NoContent) + { + var cell = new Vector3i(x, y, z); + bmp[cell] = flatResults[flatIdx]; + gpuActiveBlocks.Add(cell); + } + } + } + + int currentZ = Interlocked.Increment(ref processedZ); + if (currentZ % 10 == 0) + { + progress?.Report((0.70 + (0.30 * ((double)currentZ / gridZ)), "Grid Reconstruction...")); + } + }); - void ExecSlopes(int content) + activeBlocks = gpuActiveBlocks; + } + catch (Exception ex) + { + throw new Exception("The GPU driver failed during allocation or kernel execution. Out of VRAM or driver error.", ex); + } + } + else { - var shapeInfo = blueprint.Shapes[content]; - var probeDirectionA = -Base6Directions.Vectors[shapeInfo.Forward]; - var probeDirectionB = Base6Directions.Vectors[shapeInfo.Up]; - var supportDirectionA = -probeDirectionA; - var supportDirectionB = -probeDirectionB; + System.Diagnostics.Debug.WriteLine($"\nExecuting the model to blueprint conversion on the CPU\n"); + int tIndex = 0; - foreach (var g in bmp.Indices()) + foreach (var triangle in this.Mesh.EnumerateTriangles()) { - if (blueprint[g] != 0) - continue; + ct.ThrowIfCancellationRequested(); + if (tIndex % 1000 == 0) progress?.Report((0.7 * ((double)tIndex / triangleCount), "Triangle Evaluation...")); + tIndex++; - if - ( - blueprint[g + probeDirectionA] != BlueprintMesh.NoContent || - blueprint[g + probeDirectionB] != BlueprintMesh.NoContent - ) + var triBox = triangle.ToBox(); + foreach (var cell in Enumerators.BoxRange(triBox, indexer)) { - continue; + var cellBox = indexer.ToBox(cell); + if (cellBox.IntersectWithTriangle(triangle) != IntersectResult.NoIntersection) + { + bmp[cell] = 0; + } } + } - if (settings.SlopesMustBeSupported) + var cpuActiveBlocks = new List(); + foreach (var g in bmp.Indices()) + { + if (bmp[g] == 0) { - if - ( - //TODO: Should use face check, something symmetric - blueprint[g + supportDirectionA] != 0 || - blueprint[g + supportDirectionB] != 0 - ) + cpuActiveBlocks.Add(g); + } + } + + activeBlocks = cpuActiveBlocks; + + progress?.Report((0.70, "Slope Evaluation...")); + int totalSlopePasses = (settings.SlopesUpper ? 4 : 0) + (settings.SlopesLower ? 4 : 0) + (settings.SlopesSides ? 4 : 0); + int executedPasses = 0; + + if (settings.SlopesUpper) { ExecCpuSlopes(1); ExecCpuSlopes(2); ExecCpuSlopes(3); ExecCpuSlopes(4); } + if (settings.SlopesLower) { ExecCpuSlopes(5); ExecCpuSlopes(6); ExecCpuSlopes(7); ExecCpuSlopes(8); } + if (settings.SlopesSides) { ExecCpuSlopes(9); ExecCpuSlopes(10); ExecCpuSlopes(11); ExecCpuSlopes(12); } + + void ExecCpuSlopes(int content) + { + var shapeInfo = blueprint.Shapes[content]; + var probeDirectionA = -Base6Directions.Vectors[shapeInfo.Forward]; + var probeDirectionB = Base6Directions.Vectors[shapeInfo.Up]; + var supportDirectionA = -probeDirectionA; + var supportDirectionB = -probeDirectionB; + + Parallel.ForEach(activeBlocks, new ParallelOptions { CancellationToken = ct }, g => + { + if (blueprint[g] != 0) return; + + if (blueprint[g + probeDirectionA] != BlueprintMesh.NoContent || blueprint[g + probeDirectionB] != BlueprintMesh.NoContent) { - continue; + return; } - } - bmp[g] = content; + if (settings.SlopesMustBeSupported) + { + if (blueprint[g + supportDirectionA] != 0 || blueprint[g + supportDirectionB] != 0) + { + return; + } + } + + bmp[g] = content; + }); + + if (totalSlopePasses > 0) + { + int currentPass = Interlocked.Increment(ref executedPasses); + progress?.Report((0.70 + (0.30 * ((double)currentPass / totalSlopePasses)), "Slope Evaluation...")); + } } } + // Forcing the .NET runtime to compact the LOH and free up memory after the voxelization and slope generation, watch this space in case it hurts performance + GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce; + GC.Collect(2, GCCollectionMode.Aggressive, blocking: true, compacting: true); + + progress?.Report((1.0, "Complete, finalization!")); return blueprint; } -} -public class GridMesher -{ - public static DMesh3 Mesh(BlueprintMesh blueprint) + + // ========================================== + // PHASE 4: CPU MESHING & FINALIZATION + // (Handles VoxelSurfaceGenerator, Marching Cubes, and Text Export) + // ========================================== + public class GridMesher { - var grid = blueprint.Blocks; - - var cubes = new Bitmap3(new(grid.ni, grid.nj, grid.nk)); - foreach(var g in grid.Indices()) + public static DMesh3 Mesh(BlueprintMesh blueprint) { - cubes[g] = grid[g] == 0; - } + var grid = blueprint.Blocks; - var slopeMesh = new DMesh3(); - foreach (var g in grid.Indices()) - { - if (cubes[g]) - continue; + var cubes = new Bitmap3(new(grid.ni, grid.nj, grid.nk)); + foreach (var g in grid.Indices()) + { + cubes[g] = grid[g] == 0; + } - var shapeId = grid[g]; - if (shapeId == BlueprintMesh.NoContent) - continue; + var slopeMesh = new DMesh3(); + foreach (var g in grid.Indices()) + { + if (cubes[g]) + continue; + + var shapeId = grid[g]; + if (shapeId == BlueprintMesh.NoContent) + continue; - var shapeInfo = blueprint.Shapes[shapeId]; + var shapeInfo = blueprint.Shapes[shapeId]; - slopeMesh.AppendMesh - ( - shapeInfo.Shape, - MathRocks.ForwardUpTranslate + slopeMesh.AppendMesh ( - shapeInfo.Forward, - shapeInfo.Up, - (Vector3f) blueprint.Coords.ToBox(g).Center - ) - ); - } - - var cubesSurfaceGenerator = new VoxelSurfaceGenerator(); - cubesSurfaceGenerator.Voxels = cubes; - cubesSurfaceGenerator.Generate(); - - var cubesMesh = cubesSurfaceGenerator.Meshes[0]; - MeshTransforms.Scale(cubesMesh, blueprint.Coords.CellSize); + shapeInfo.Shape, + MathRocks.ForwardUpTranslate + ( + shapeInfo.Forward, + shapeInfo.Up, + (Vector3f)blueprint.Coords.ToBox(g).Center + ) + ); + } - // Voxel generator generates around UnitZeroCentered, while indexer rounds down to corner - var correctionOffset = blueprint.Coords.CellSize / 2; - MeshTransforms.Translate(cubesMesh, blueprint.Coords.Origin + correctionOffset); + var cubesSurfaceGenerator = new VoxelSurfaceGenerator(); + cubesSurfaceGenerator.Voxels = cubes; + cubesSurfaceGenerator.Generate(); + var cubesMesh = cubesSurfaceGenerator.Meshes[0]; + MeshTransforms.Scale(cubesMesh, blueprint.Coords.CellSize); - var finalMesh = cubesMesh; - finalMesh.AppendMesh(slopeMesh); + var correctionOffset = blueprint.Coords.CellSize / 2; + MeshTransforms.Translate(cubesMesh, blueprint.Coords.Origin + correctionOffset); - return finalMesh; - } -} + var finalMesh = cubesMesh; + finalMesh.AppendMesh(slopeMesh); -public class ShapeDB -{ - public const float LargeBlockSize = 2.5f; - public const float MidBlockSize = 0.5f; - public const float SmallBlockSize = 0.25f; + return finalMesh; + } + } - public record ShapeInfo + public class ShapeDB { - public DMesh3 Shape; - public string Prefab; + public const float LargeBlockSize = 2.5f; + public const float MidBlockSize = 0.5f; + public const float SmallBlockSize = 0.25f; - public int Forward = Base6Directions.Forward; - public int Up = Base6Directions.Up; - } + public record ShapeInfo + { + public DMesh3 Shape; + public string Prefab; - public ShapeInfo[] Shapes { get; } - public ShapeInfo this[int index] => this.Shapes[index]; + public int Forward = Base6Directions.Forward; + public int Up = Base6Directions.Up; + } - public ShapeDB(params ShapeInfo[] shapes) - { - this.Shapes = shapes; - } + public ShapeInfo[] Shapes { get; } + public ShapeInfo this[int index] => this.Shapes[index]; - public static ShapeDB LargeShapes = new - ( - // Cube - CubicShape("2eacbbf2-d8fb-4a78-91dc-7b492517ef97", x => x.AppendBox(Dims(LargeBlockSize))), - - // Slopes - SlopeShape("f9efcc6c-6c76-4762-bbf0-6013ec969539", LargeBlockSize, Base6Directions.Left, Base6Directions.Up), - SlopeShape("f9efcc6c-6c76-4762-bbf0-6013ec969539", LargeBlockSize, Base6Directions.Right, Base6Directions.Up), - SlopeShape("f9efcc6c-6c76-4762-bbf0-6013ec969539", LargeBlockSize, Base6Directions.Forward, Base6Directions.Up), - SlopeShape("f9efcc6c-6c76-4762-bbf0-6013ec969539", LargeBlockSize, Base6Directions.Backward, Base6Directions.Up), - - SlopeShape("f9efcc6c-6c76-4762-bbf0-6013ec969539", LargeBlockSize, Base6Directions.Left, Base6Directions.Down), - SlopeShape("f9efcc6c-6c76-4762-bbf0-6013ec969539", LargeBlockSize, Base6Directions.Right, Base6Directions.Down), - SlopeShape("f9efcc6c-6c76-4762-bbf0-6013ec969539", LargeBlockSize, Base6Directions.Forward, Base6Directions.Down), - SlopeShape("f9efcc6c-6c76-4762-bbf0-6013ec969539", LargeBlockSize, Base6Directions.Backward, Base6Directions.Down), - - SlopeShape("f9efcc6c-6c76-4762-bbf0-6013ec969539", LargeBlockSize, Base6Directions.Forward, Base6Directions.Left), - SlopeShape("f9efcc6c-6c76-4762-bbf0-6013ec969539", LargeBlockSize, Base6Directions.Left, Base6Directions.Backward), - SlopeShape("f9efcc6c-6c76-4762-bbf0-6013ec969539", LargeBlockSize, Base6Directions.Backward, Base6Directions.Right), - SlopeShape("f9efcc6c-6c76-4762-bbf0-6013ec969539", LargeBlockSize, Base6Directions.Right, Base6Directions.Forward) - ); - - public static ShapeDB MidShapes = new - ( - // Cube - CubicShape("632d7385-12b9-47a6-802a-a610d0cbd1e0", x => x.AppendBox(Dims(MidBlockSize))), - - // Slopes - SlopeShape("69902790-3e2d-43d2-81e4-1c0b42bc7461", MidBlockSize, Base6Directions.Left, Base6Directions.Up), - SlopeShape("69902790-3e2d-43d2-81e4-1c0b42bc7461", MidBlockSize, Base6Directions.Right, Base6Directions.Up), - SlopeShape("69902790-3e2d-43d2-81e4-1c0b42bc7461", MidBlockSize, Base6Directions.Forward, Base6Directions.Up), - SlopeShape("69902790-3e2d-43d2-81e4-1c0b42bc7461", MidBlockSize, Base6Directions.Backward, Base6Directions.Up), - - SlopeShape("69902790-3e2d-43d2-81e4-1c0b42bc7461", MidBlockSize, Base6Directions.Left, Base6Directions.Down), - SlopeShape("69902790-3e2d-43d2-81e4-1c0b42bc7461", MidBlockSize, Base6Directions.Right, Base6Directions.Down), - SlopeShape("69902790-3e2d-43d2-81e4-1c0b42bc7461", MidBlockSize, Base6Directions.Forward, Base6Directions.Down), - SlopeShape("69902790-3e2d-43d2-81e4-1c0b42bc7461", MidBlockSize, Base6Directions.Backward, Base6Directions.Down), - - SlopeShape("69902790-3e2d-43d2-81e4-1c0b42bc7461", MidBlockSize, Base6Directions.Forward, Base6Directions.Left), - SlopeShape("69902790-3e2d-43d2-81e4-1c0b42bc7461", MidBlockSize, Base6Directions.Left, Base6Directions.Backward), - SlopeShape("69902790-3e2d-43d2-81e4-1c0b42bc7461", MidBlockSize, Base6Directions.Backward, Base6Directions.Right), - SlopeShape("69902790-3e2d-43d2-81e4-1c0b42bc7461", MidBlockSize, Base6Directions.Right, Base6Directions.Forward) - ); - - private static ShapeInfo SlopeShape(string prefab, float size, int forward, int up) - { - var info = CubicShape + public ShapeDB(params ShapeInfo[] shapes) + { + this.Shapes = shapes; + } + + public static ShapeDB LargeShapes = new ( - prefab, - x => - { - x.AppendSlope - ( - Dims(size), - Base6Directions.Vectors[forward], - -Base6Directions.Vectors[up] - ); - } + CubicShape("2eacbbf2-d8fb-4a78-91dc-7b492517ef97", x => x.AppendBox(Dims(LargeBlockSize))), + + SlopeShape("f9efcc6c-6c76-4762-bbf0-6013ec969539", LargeBlockSize, Base6Directions.Left, Base6Directions.Up), + SlopeShape("f9efcc6c-6c76-4762-bbf0-6013ec969539", LargeBlockSize, Base6Directions.Right, Base6Directions.Up), + SlopeShape("f9efcc6c-6c76-4762-bbf0-6013ec969539", LargeBlockSize, Base6Directions.Forward, Base6Directions.Up), + SlopeShape("f9efcc6c-6c76-4762-bbf0-6013ec969539", LargeBlockSize, Base6Directions.Backward, Base6Directions.Up), + + SlopeShape("f9efcc6c-6c76-4762-bbf0-6013ec969539", LargeBlockSize, Base6Directions.Left, Base6Directions.Down), + SlopeShape("f9efcc6c-6c76-4762-bbf0-6013ec969539", LargeBlockSize, Base6Directions.Right, Base6Directions.Down), + SlopeShape("f9efcc6c-6c76-4762-bbf0-6013ec969539", LargeBlockSize, Base6Directions.Forward, Base6Directions.Down), + SlopeShape("f9efcc6c-6c76-4762-bbf0-6013ec969539", LargeBlockSize, Base6Directions.Backward, Base6Directions.Down), + + SlopeShape("f9efcc6c-6c76-4762-bbf0-6013ec969539", LargeBlockSize, Base6Directions.Forward, Base6Directions.Left), + SlopeShape("f9efcc6c-6c76-4762-bbf0-6013ec969539", LargeBlockSize, Base6Directions.Left, Base6Directions.Backward), + SlopeShape("f9efcc6c-6c76-4762-bbf0-6013ec969539", LargeBlockSize, Base6Directions.Backward, Base6Directions.Right), + SlopeShape("f9efcc6c-6c76-4762-bbf0-6013ec969539", LargeBlockSize, Base6Directions.Right, Base6Directions.Forward) ); - info.Up = up; - info.Forward = forward; - return info; - } + public static ShapeDB MidShapes = new + ( + CubicShape("632d7385-12b9-47a6-802a-a610d0cbd1e0", x => x.AppendBox(Dims(MidBlockSize))), + + SlopeShape("69902790-3e2d-43d2-81e4-1c0b42bc7461", MidBlockSize, Base6Directions.Left, Base6Directions.Up), + SlopeShape("69902790-3e2d-43d2-81e4-1c0b42bc7461", MidBlockSize, Base6Directions.Right, Base6Directions.Up), + SlopeShape("69902790-3e2d-43d2-81e4-1c0b42bc7461", MidBlockSize, Base6Directions.Forward, Base6Directions.Up), + SlopeShape("69902790-3e2d-43d2-81e4-1c0b42bc7461", MidBlockSize, Base6Directions.Backward, Base6Directions.Up), + + SlopeShape("69902790-3e2d-43d2-81e4-1c0b42bc7461", MidBlockSize, Base6Directions.Left, Base6Directions.Down), + SlopeShape("69902790-3e2d-43d2-81e4-1c0b42bc7461", MidBlockSize, Base6Directions.Right, Base6Directions.Down), + SlopeShape("69902790-3e2d-43d2-81e4-1c0b42bc7461", MidBlockSize, Base6Directions.Forward, Base6Directions.Down), + SlopeShape("69902790-3e2d-43d2-81e4-1c0b42bc7461", MidBlockSize, Base6Directions.Backward, Base6Directions.Down), + + SlopeShape("69902790-3e2d-43d2-81e4-1c0b42bc7461", MidBlockSize, Base6Directions.Forward, Base6Directions.Left), + SlopeShape("69902790-3e2d-43d2-81e4-1c0b42bc7461", MidBlockSize, Base6Directions.Left, Base6Directions.Backward), + SlopeShape("69902790-3e2d-43d2-81e4-1c0b42bc7461", MidBlockSize, Base6Directions.Backward, Base6Directions.Right), + SlopeShape("69902790-3e2d-43d2-81e4-1c0b42bc7461", MidBlockSize, Base6Directions.Right, Base6Directions.Forward) + ); - private static ShapeInfo CubicShape(string prefab, Action shape) - { - return new() + private static ShapeInfo SlopeShape(string prefab, float size, int forward, int up) { - Prefab = prefab, - Shape = MakeShape(shape) - }; - } + var info = CubicShape + ( + prefab, + x => + { + x.AppendSlope + ( + Dims(size), + Base6Directions.Vectors[forward], + -Base6Directions.Vectors[up] + ); + } + ); - private static AxisAlignedBox3f Dims(float size) - { - return new(Vector3f.Zero, size / 2); - } + info.Up = up; + info.Forward = forward; + return info; + } - private static DMesh3 MakeShape(Action factory) - { - var mesh = new DMesh3(); - factory(mesh); - return mesh; - } -} + private static ShapeInfo CubicShape(string prefab, Action shape) + { + return new() + { + Prefab = prefab, + Shape = MakeShape(shape) + }; + } -public class BlueprintMesh -{ - public const int NoContent = int.MaxValue; + private static AxisAlignedBox3f Dims(float size) + { + return new(Vector3f.Zero, size / 2); + } - public DenseGrid3i Blocks; - public ShiftGridIndexer3 Coords; - public ShapeDB Shapes; + private static DMesh3 MakeShape(Action factory) + { + var mesh = new DMesh3(); + factory(mesh); + return mesh; + } + } - public int this[Vector3i index] + public class BlueprintMesh { - get + public const int NoContent = int.MaxValue; + + public DenseGrid3i Blocks; + public ShiftGridIndexer3 Coords; + public ShapeDB Shapes; + + public int this[Vector3i index] { - if (this.Blocks.IsValidIndex(index) == false) - return NoContent; + get + { + if (this.Blocks.IsValidIndex(index) == false) + return NoContent; - return this.Blocks[index]; + return this.Blocks[index]; + } } } -} - -public class BlueprintWriter -{ - public required string WriteFolder { get; set; } - public void Write(BlueprintMesh blueprint, string name) + public class BlueprintWriter { - var sb = new StringBuilder(); - Generate(blueprint, sb); - File.WriteAllText(Path.Combine(this.WriteFolder, $"{name}.txt"), sb.ToString()); - } + public required string WriteFolder { get; set; } - public void Generate(BlueprintMesh blueprint, StringBuilder sb) - { - //Prefab|PositionX|PositionY|PositionZ|ColorHUE|ColorSATURATION|ColorVALUE|OrientationFORWARD|OrientationUP|Integrity - var blockGrid = blueprint.Blocks; - foreach (var g in blockGrid.Indices()) + public void Write(BlueprintMesh blueprint, string name) { - var content = blockGrid[g]; - if (content == BlueprintMesh.NoContent) - continue; + var sb = new StringBuilder(); + Generate(blueprint, sb); + File.WriteAllText(Path.Combine(this.WriteFolder, $"{name}.txt"), sb.ToString()); + } - if (content < 0) + public void Generate(BlueprintMesh blueprint, StringBuilder sb) + { + var blockGrid = blueprint.Blocks; + foreach (var g in blockGrid.Indices()) { - throw new NotImplementedException("Shape lists will go here"); + var content = blockGrid[g]; + if (content == BlueprintMesh.NoContent) + continue; + + if (content < 0) + { + throw new NotImplementedException("Shape lists will go here"); + } + + var block = blueprint.Shapes[content]; + sb.Append(block.Prefab); + sb.Append('|'); + + var forwardAxis = block.Forward; + var upAxis = block.Up; + + var cube = blueprint.Coords.ToBox(g); + var gridPosition = ToInt(cube.Center / ShapeDB.SmallBlockSize); + gridPosition += PositionOffset + ( + blueprint.Shapes == ShapeDB.LargeShapes ? + new AxisAlignedBox3i(new Vector3i(-4, -4, -4), new Vector3i(5, 5, 5)) : + new AxisAlignedBox3i(new Vector3i(0, 0, 0), new Vector3i(1, 1, 1)), + forwardAxis, + upAxis + ); + + sb.Append(gridPosition.x); + sb.Append('|'); + sb.Append(gridPosition.y); + sb.Append('|'); + sb.Append(gridPosition.z); + sb.Append('|'); + + sb.Append(0); + sb.Append('|'); + sb.Append(0); + sb.Append('|'); + sb.Append(0.25); + sb.Append('|'); + + sb.Append(forwardAxis); + sb.Append('|'); + sb.Append(upAxis); + sb.Append('|'); + + sb.Append(1); + sb.Append('|'); + + sb.AppendLine(); } - var block = blueprint.Shapes[content]; - sb.Append(block.Prefab); - sb.Append('|'); + Vector3i PositionOffset(AxisAlignedBox3i blockSize, int blockForward, int blockRight) + { + var baseRight = Base6Directions.Vectors[blockRight]; + var baseForward = -Base6Directions.Vectors[blockForward]; + return BlockOffset + ( + blockSize, + new Matrix3f + ( + (Vector3f)baseRight.Cross(baseForward), + (Vector3f)baseRight, + (Vector3f)baseForward, + bRows: false + ) + ); + } - var forwardAxis = block.Forward; - var upAxis = block.Up; + static Vector3i BlockOffset(AxisAlignedBox3i blockSize, Matrix3f blockOrientation) + { + var offsetNegative = (Vector3f)blockSize.Min; + var offsetPositive = (Vector3f)blockSize.Max; - var cube = blueprint.Coords.ToBox(g); - var gridPosition = ToInt(cube.Center / ShapeDB.SmallBlockSize); - gridPosition += PositionOffset - ( - //TODO: - blueprint.Shapes == ShapeDB.LargeShapes ? - new AxisAlignedBox3i(new Vector3i(-4, -4, -4), new Vector3i(5, 5, 5)) : - new AxisAlignedBox3i(new Vector3i(0, 0, 0), new Vector3i(1, 1, 1)), - forwardAxis, - upAxis - ); + var a = ToInt(blockOrientation.Multiply(ref offsetNegative)); + var b = ToInt(blockOrientation.Multiply(ref offsetPositive)); + + var minI = new Vector3i(Math.Min(a.x, b.x), Math.Min(a.y, b.y), Math.Min(a.z, b.z)); + return blockSize.Min - minI; + } + static Vector3i ToInt(Vector3d vec) + { + return new + ( + (int)Math.Round(vec.x), + (int)Math.Round(vec.y), + (int)Math.Round(vec.z) + ); + } + } + } + + public static class VoxelizationKernel + { + private static int Min(int a, int b) => a < b ? a : b; + private static int Max(int a, int b) => a > b ? a : b; + private static float Min(float a, float b) => a < b ? a : b; + private static float Max(float a, float b) => a > b ? a : b; + private static float Abs(float v) => v < 0f ? -v : v; + private static float Min3(float a, float b, float c) => Min(a, Min(b, c)); + private static float Max3(float a, float b, float c) => Max(a, Max(b, c)); + private static int Floor(float val) => val < 0f ? (int)val - 1 : (int)val; + private static int Ceiling(float val) => val > (int)val ? (int)val + 1 : (int)val; + + public static void InitializeGrid(Index1D index, ArrayView grid, int value) + { + grid[index] = value; + } + + public static void Voxelize( + Index1D index, + ArrayView triangles, + ArrayView voxelGrid, + Float3 gridOrigin, + float cellSize, + int gridX, + int gridY, + int gridZ) + { + var tri = triangles[index]; - sb.Append(gridPosition.x); - sb.Append('|'); - sb.Append(gridPosition.y); - sb.Append('|'); - sb.Append(gridPosition.z); - sb.Append('|'); + int minX = Max(0, Floor((tri.MinBounds.X - gridOrigin.X) / cellSize)); + int minY = Max(0, Floor((tri.MinBounds.Y - gridOrigin.Y) / cellSize)); + int minZ = Max(0, Floor((tri.MinBounds.Z - gridOrigin.Z) / cellSize)); - sb.Append(0); - sb.Append('|'); - sb.Append(0); - sb.Append('|'); - sb.Append(0.25); - sb.Append('|'); + int maxX = Min(gridX - 1, Ceiling((tri.MaxBounds.X - gridOrigin.X) / cellSize)); + int maxY = Min(gridY - 1, Ceiling((tri.MaxBounds.Y - gridOrigin.Y) / cellSize)); + int maxZ = Min(gridZ - 1, Ceiling((tri.MaxBounds.Z - gridOrigin.Z) / cellSize)); - sb.Append(forwardAxis); - sb.Append('|'); - sb.Append(upAxis); - sb.Append('|'); + float e0X = tri.V1.X - tri.V0.X; float e0Y = tri.V1.Y - tri.V0.Y; float e0Z = tri.V1.Z - tri.V0.Z; + float e1X = tri.V2.X - tri.V1.X; float e1Y = tri.V2.Y - tri.V1.Y; float e1Z = tri.V2.Z - tri.V1.Z; + float e2X = tri.V0.X - tri.V2.X; float e2Y = tri.V0.Y - tri.V2.Y; float e2Z = tri.V0.Z - tri.V2.Z; - sb.Append(1); - sb.Append('|'); + float normalX = e0Y * e1Z - e0Z * e1Y; + float normalY = e0Z * e1X - e0X * e1Z; + float normalZ = e0X * e1Y - e0Y * e1X; - sb.AppendLine(); + for (int z = minZ; z <= maxZ; z++) + { + for (int y = minY; y <= maxY; y++) + { + for (int x = minX; x <= maxX; x++) + { + Float3 cellCenter = new Float3( + gridOrigin.X + (x + 0.5f) * cellSize, + gridOrigin.Y + (y + 0.5f) * cellSize, + gridOrigin.Z + (z + 0.5f) * cellSize + ); + + if (CheckTriangleBoxIntersection(tri, cellCenter, cellSize, + e0X, e0Y, e0Z, e1X, e1Y, e1Z, e2X, e2Y, e2Z, normalX, normalY, normalZ)) + { + int flatIndex = x + (y * gridX) + (z * gridX * gridY); + + if (voxelGrid[flatIndex] != 0) + { + Atomic.Exchange(ref voxelGrid[flatIndex], 0); + } + } + } + } + } } - Vector3i PositionOffset(AxisAlignedBox3i blockSize, int blockForward, int blockRight) + public static void GenerateSlopes( + Index1D index, + ArrayView voxelGrid, + GpuSlopeParams p) { - var baseRight = Base6Directions.Vectors[blockRight]; - var baseForward = -Base6Directions.Vectors[blockForward]; - return BlockOffset - ( - blockSize, - new Matrix3f - ( - (Vector3f) baseRight.Cross(baseForward), - (Vector3f) baseRight, - (Vector3f) baseForward, - bRows: false - ) - ); + int x = index % p.GridX; + int y = (index / p.GridX) % p.GridY; + int z = index / (p.GridX * p.GridY); + + if (voxelGrid[index] != 0) return; + + int nxA = x + p.PAx; int nyA = y + p.PAy; int nzA = z + p.PAz; + int nxB = x + p.PBx; int nyB = y + p.PBy; int nzB = z + p.PBz; + + if (nxA < 0 || nxA >= p.GridX || nyA < 0 || nyA >= p.GridY || nzA < 0 || nzA >= p.GridZ) return; + if (nxB < 0 || nxB >= p.GridX || nyB < 0 || nyB >= p.GridY || nzB < 0 || nzB >= p.GridZ) return; + + int idxA = nxA + (nyA * p.GridX) + (nzA * p.GridX * p.GridY); + int idxB = nxB + (nyB * p.GridX) + (nzB * p.GridX * p.GridY); + + if (voxelGrid[idxA] != int.MaxValue || voxelGrid[idxB] != int.MaxValue) + return; + + if (p.MustBeSupported == 1) + { + int sxA = x + p.SAx; int syA = y + p.SAy; int szA = z + p.SAz; + int sxB = x + p.SBx; int syB = y + p.SBy; int szB = z + p.SBz; + + if (sxA < 0 || sxA >= p.GridX || syA < 0 || syA >= p.GridY || szA < 0 || szA >= p.GridZ) return; + if (sxB < 0 || sxB >= p.GridX || syB < 0 || syB >= p.GridY || szB < 0 || szB >= p.GridZ) return; + + int sIdxA = sxA + (syA * p.GridX) + (szA * p.GridX * p.GridY); + int sIdxB = sxB + (syB * p.GridX) + (szB * p.GridX * p.GridY); + + if (voxelGrid[sIdxA] != 0 || voxelGrid[sIdxB] != 0) + return; + } + + Atomic.Exchange(ref voxelGrid[index], p.Content); } - static Vector3i BlockOffset(AxisAlignedBox3i blockSize, Matrix3f blockOrientation) + private static bool CheckTriangleBoxIntersection( + GpuTriangle tri, Float3 boxCenter, float cellSize, + float e0X, float e0Y, float e0Z, + float e1X, float e1Y, float e1Z, + float e2X, float e2Y, float e2Z, + float normalX, float normalY, float normalZ) { - var offsetNegative = (Vector3f) blockSize.Min; - var offsetPositive = (Vector3f) blockSize.Max; + float boxHalf = cellSize * 0.5f; + + float v0X = tri.V0.X - boxCenter.X; float v0Y = tri.V0.Y - boxCenter.Y; float v0Z = tri.V0.Z - boxCenter.Z; + float v1X = tri.V1.X - boxCenter.X; float v1Y = tri.V1.Y - boxCenter.Y; float v1Z = tri.V1.Z - boxCenter.Z; + float v2X = tri.V2.X - boxCenter.X; float v2Y = tri.V2.Y - boxCenter.Y; float v2Z = tri.V2.Z - boxCenter.Z; + + if (Min3(v0X, v1X, v2X) > boxHalf || Max3(v0X, v1X, v2X) < -boxHalf) return false; + if (Min3(v0Y, v1Y, v2Y) > boxHalf || Max3(v0Y, v1Y, v2Y) < -boxHalf) return false; + if (Min3(v0Z, v1Z, v2Z) > boxHalf || Max3(v0Z, v1Z, v2Z) < -boxHalf) return false; - var a = ToInt(blockOrientation.Multiply(ref offsetNegative)); - var b = ToInt(blockOrientation.Multiply(ref offsetPositive)); + float d = -(normalX * v0X + normalY * v0Y + normalZ * v0Z); - var minI = new Vector3i(Math.Min(a.x, b.x), Math.Min(a.y, b.y), Math.Min(a.z, b.z)); - return blockSize.Min - minI; + float vminX = normalX > 0f ? -boxHalf : boxHalf; float vmaxX = normalX > 0f ? boxHalf : -boxHalf; + float vminY = normalY > 0f ? -boxHalf : boxHalf; float vmaxY = normalY > 0f ? boxHalf : -boxHalf; + float vminZ = normalZ > 0f ? -boxHalf : boxHalf; float vmaxZ = normalZ > 0f ? boxHalf : -boxHalf; + + if ((normalX * vminX + normalY * vminY + normalZ * vminZ) + d > 0f) return false; + if ((normalX * vmaxX + normalY * vmaxY + normalZ * vmaxZ) + d < 0f) return false; + + if (!AxisTest(e0Z, -e0Y, v0Y, v0Z, v2Y, v2Z, boxHalf)) return false; + if (!AxisTest(e1Z, -e1Y, v1Y, v1Z, v0Y, v0Z, boxHalf)) return false; + if (!AxisTest(e2Z, -e2Y, v2Y, v2Z, v1Y, v1Z, boxHalf)) return false; + + if (!AxisTest(-e0Z, e0X, v0X, v0Z, v2X, v2Z, boxHalf)) return false; + if (!AxisTest(-e1Z, e1X, v1X, v1Z, v0X, v0Z, boxHalf)) return false; + if (!AxisTest(-e2Z, e2X, v2X, v2Z, v1X, v1Z, boxHalf)) return false; + + if (!AxisTest(e0Y, -e0X, v0X, v0Y, v2X, v2Y, boxHalf)) return false; + if (!AxisTest(e1Y, -e1X, v1X, v1Y, v0X, v0Y, boxHalf)) return false; + if (!AxisTest(e2Y, -e2X, v2X, v2Y, v1X, v1Y, boxHalf)) return false; + + return true; } - static Vector3i ToInt(Vector3d vec) + private static bool AxisTest(float a, float b, float fa, float fb, float va, float vb, float boxHalf) { - return new - ( - (int) Math.Round(vec.x), - (int) Math.Round(vec.y), - (int) Math.Round(vec.z) - ); + float p0 = a * fa + b * fb; + float p2 = a * va + b * vb; + float min = Min(p0, p2); + float max = Max(p0, p2); + float rad = (Abs(a) + Abs(b)) * boxHalf; + return !(min > rad || max < -rad); } } } \ No newline at end of file diff --git a/App.xaml.cs b/App.xaml.cs index 1855bdf..f3d8e3d 100644 --- a/App.xaml.cs +++ b/App.xaml.cs @@ -1,7 +1,5 @@ -using System.Configuration; -using System.Data; +using SpaceEditor.Data; using System.Windows; -using SpaceEditor.Data; namespace SpaceEditor { diff --git a/Controls/AsyncView.xaml.cs b/Controls/AsyncView.xaml.cs index bc13aae..8e02a5c 100644 --- a/Controls/AsyncView.xaml.cs +++ b/Controls/AsyncView.xaml.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Windows; +using System.Windows; using System.Windows.Controls; namespace SpaceEditor.Controls; @@ -15,7 +11,7 @@ public AsyncView() { this.DataContextChanged += OnDataContextChanged; this.Unloaded += OnUnloaded; - + InitializeComponent(); } @@ -34,7 +30,7 @@ private async void OnDataContextChanged(object sender, DependencyPropertyChanged this.Lifetime?.Cancel(); this.Lifetime = new(); - + var lifetime = this.Lifetime.Token; this.LoadingContent.Visibility = Visibility.Visible; @@ -45,7 +41,7 @@ private async void OnDataContextChanged(object sender, DependencyPropertyChanged try { await data.WaitAsync(lifetime); - + this.LoadingContent.Visibility = Visibility.Collapsed; this.MainContent.Visibility = Visibility.Visible; } diff --git a/Controls/BlueprintGenerator.xaml b/Controls/BlueprintGenerator.xaml index 32dd696..0161b83 100644 --- a/Controls/BlueprintGenerator.xaml +++ b/Controls/BlueprintGenerator.xaml @@ -6,7 +6,8 @@ xmlns:local="clr-namespace:SpaceEditor.Controls" xmlns:colorPicker="clr-namespace:ColorPicker;assembly=ColorPicker" mc:Ignorable="d" - d:DesignHeight="450" d:DesignWidth="800"> + d:DesignHeight="450" d:DesignWidth="800" + PreviewKeyDown="UserControl_PreviewKeyDown"> @@ -47,10 +48,40 @@ - + + + + + update) { + // Block model modifications while a generation is running + // Prevents the accidental cancellation of a generation due to a button press in the blueprint menu + if (this.Screen.IsGenerating) return; + var model = this.Screen.Model; if (model is null) return; @@ -113,20 +115,31 @@ private void UpdateModelCopy(Action update) /// public partial class BlueprintGenerator : UserControl { + public bool IsGenerating { get; private set; } = false; + public DMesh3? Model; public AsyncLazy? ModelBVH; public CancellationTokenSource? ModelLifetime; - + public CancellationTokenSource? GeneratorLifetime; + private void UserControl_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e) + { + if (e.Key == System.Windows.Input.Key.Escape) + { + this.GeneratorLifetime?.Cancel(); + e.Handled = true; + } + } + private ModelSettings ModelSettingsVM { - get => (ModelSettings) this.ModelSettings.ReflectedInstance; + get => (ModelSettings)this.ModelSettings.ReflectedInstance; set { // Make sure to reload the new values this.ModelSettings.ReflectedInstance = null!; - + this.ModelSettings.ReflectedInstance = value; } } @@ -143,6 +156,9 @@ public BlueprintGenerator() private void SelectModel(object sender, RoutedEventArgs e) { + // Block new file selection while a generation is running + if (this.IsGenerating) return; + var selectFile = new OpenFileDialog(); if (selectFile.ShowDialog(Window.GetWindow(this)) != true) return; @@ -153,7 +169,7 @@ private void SelectModel(object sender, RoutedEventArgs e) model = LoadModel(selectFile.FileName); this.BlueprintName.Text = Path.GetFileNameWithoutExtension(selectFile.FileName); - + } catch @@ -183,7 +199,7 @@ public void SetNewModel(DMesh3? model) this.ModelBVH.Poke(); var bb = this.Model.CachedBounds; - this.ModelSettingsVM = this.ModelSettingsVM with { ModelSize = (float) bb.Extents.MaxAbs * 2 }; + this.ModelSettingsVM = this.ModelSettingsVM with { ModelSize = (float)bb.Extents.MaxAbs * 2 }; var modelInfo = new StringBuilder(); modelInfo.AppendLine($"Model: {this.BlueprintName.Text}"); @@ -192,12 +208,12 @@ public void SetNewModel(DMesh3? model) var modelSize = Math.Max(bb.Min.MaxAbs, bb.Max.MaxAbs) * 2; if (modelSize > 3000) { - modelInfo.AppendLine("Mode is too larget to safely convert."); + modelInfo.AppendLine("Model is too large to safely convert."); modelInfo.AppendLine("Scale it down."); } else if (modelSize > 500) { - modelInfo.AppendLine("Mode is quite large, performance issues may appear."); + modelInfo.AppendLine("Model is quite large, performance issues may appear."); modelInfo.AppendLine("Recommended to scale it down."); } @@ -220,23 +236,84 @@ public void SetNewModel(DMesh3? model) private async void GenerateBlueprint(object sender, RoutedEventArgs e) { + string mode; + // Prevent accidental double-clicks from spawning multiple tasks + if (this.IsGenerating) return; + this.IsGenerating = true; + try { + // Capture the state of the Shift key at the exact moment of the click + bool isCpuFallback = System.Windows.Input.Keyboard.Modifiers.HasFlag(System.Windows.Input.ModifierKeys.Shift); + var lifetime = ResetLifetime(ref this.GeneratorLifetime, this.ModelLifetime); var model = this.Model; if (model is null) return; - + var tree = await this.ModelBVH!.Value; - var shaper = new GridShaper(model, tree); - var blueprint = shaper.Generate - ( - (GridShaper.GeneratorSettings) this.GeneratorSettings.ReflectedInstance, - lifetime - ); + var settings = (GridShaper.GeneratorSettings)this.GeneratorSettings.ReflectedInstance; + + // Hide the button and show the progress overlay + this.GenerateButton.Visibility = Visibility.Collapsed; + this.ProgressOverlay.Visibility = Visibility.Visible; + this.GenerateProgress.Value = 0; + this.ProgressText.Visibility = Visibility.Visible; // Explicitly show the text + this.ProgressText.Text = "Initializing..."; + + // Without this, the esc button dosen't work because the bluepring generation button was colapsed to make place for the progress bar + this.Focusable = true; + System.Windows.Input.Keyboard.Focus(this); + + // Display the CPU/GPU indicator text + this.BlueprintDetails.Text = isCpuFallback ? "Generating via CPU (Shift fallback)..." : "Generating via GPU..."; + + // Route background progress updates back to the UI thread + var progress = new Progress<(double Value, string Message)>(p => + { + this.GenerateProgress.Value = p.Value * 100; + this.ProgressText.Text = p.Message; + }); + BlueprintMesh blueprint; + + // Start the high-precision stopwatch right before dispatching + var stopwatch = System.Diagnostics.Stopwatch.StartNew(); + + try + { + // Offload execution to a background thread, routing to CPU or GPU based on the flag + blueprint = await Task.Run(() => + { + if (isCpuFallback) + { + return shaper.GenerateCpu(settings, lifetime, progress); + } + else + { + return shaper.Generate(settings, lifetime, progress); + } + }, lifetime); + } + finally + { + // Guarantee the UI resets and the generate button is visible even if generation fails or is cancelled + this.GenerateButton.Visibility = Visibility.Visible; + + // Explicitly collapse the entire overlay grid, NOT just the inner progress bar + this.ProgressOverlay.Visibility = Visibility.Collapsed; + + // Explicitly hide and clear the text block + this.ProgressText.Visibility = Visibility.Collapsed; + this.ProgressText.Text = string.Empty; + + // Do NOT unlock IsGenerating here or stop the stopwatch yet. + // The UI thread still needs to build and render the mesh! + } + + // The code below automatically resumes on the UI thread var gridMesh = GridMesher.Mesh(blueprint); var modelRender = CreateRenderModel(gridMesh, controlsRow: 1); lifetime.Register(() => @@ -252,17 +329,33 @@ private async void GenerateBlueprint(object sender, RoutedEventArgs e) Vector3i dimensions = GetBlueprintDiemensions(usedIndicies); - string info = $"Blocks: X: {dimensions.x} Y: {dimensions.y} Z: {dimensions.z} Total: {usedIndicies.Count()}"; - this.BlueprintDetails.Text = info; + // The meshing and viewport rendering is finished. Now we stop the stopwatch. + stopwatch.Stop(); + + mode = isCpuFallback ? "CPU" : "GPU"; + System.Diagnostics.Debug.WriteLine($"\n[PERFORMANCE] Blueprint generated in {stopwatch.Elapsed.TotalSeconds:F3} seconds using {mode}.\n"); + + string info = $"Blocks: X: {dimensions.x} Y: {dimensions.y} Z: {dimensions.z} Total: {usedIndicies.Count()}\nTime: {stopwatch.Elapsed.TotalSeconds:F2}s (generated using {mode})"; + this.BlueprintDetails.Text = info; lifetime.Register(() => { this.BlueprintDetails.Text = null; }); } - catch + catch (OperationCanceledException) + { + // Silently catch the cancellation so it doesn't crash the app + this.BlueprintDetails.Text = "Generation cancelled."; + } + catch (Exception ex) + { + // Handle other general exceptions + this.BlueprintDetails.Text = "Generation failed. See logs."; + } + finally { - + this.IsGenerating = false; // Failsafe unlock AFTER everything is done } } @@ -270,14 +363,14 @@ public static Vector3i GetBlueprintDiemensions(IEnumerable indicies) { AxisAlignedBox3i dimensions = AxisAlignedBox3i.Empty; - foreach (Vector3i v3i in indicies) + foreach (Vector3i v3i in indicies) { dimensions.Contain(v3i); } return dimensions.Diagonal + 1; } - + private void ExportBlueprint(object sender, RoutedEventArgs e) { var blueprint = (BlueprintMesh)this.ExportBlueprintPanel.Tag; @@ -306,10 +399,10 @@ private void ExportBlueprintAsModel(object sender, RoutedEventArgs e) saveLocation.AddExtension = true; saveLocation.DefaultExt = "obj"; saveLocation.FileName = this.BlueprintName.Text; - + if (saveLocation.ShowDialog() != true) return; - + var mesh = GridMesher.Mesh(blueprint); Util.WriteDebugMesh(mesh, saveLocation.FileName); } @@ -324,7 +417,7 @@ private DMesh3 LoadModel(string path) foreach (var m in scene.Meshes) { var vertexIndices = new List(); - + var vertices = m.Vertices; var normals = m.HasNormals ? m.Normals : null; var uvs = m.HasTextureCoords(0) ? m.TextureCoordinateChannels[0] : null; @@ -399,15 +492,15 @@ private IDisposable CreateRenderModel(DMesh3 model, int controlsRow) { var controls = this.ViewportControls.Children.OfType().Where(x => Grid.GetRow(x) == controlsRow).ToArray(); - var draw = (CheckBox) controls[1]; - var xray = (CheckBox) controls[2]; - var color = (ColourSlider) controls[3]; + var draw = (CheckBox)controls[1]; + var xray = (CheckBox)controls[2]; + var color = (ColourSlider)controls[3]; var renderData = new ModelViewport.ModelData { Mesh = model, }; - + RoutedEventHandler onCheckedChanged = (_, _) => { renderData.Color = color.SelectedColour; diff --git a/Controls/ButtonPropertyGridControlFactory.cs b/Controls/ButtonPropertyGridControlFactory.cs index 184dcfc..1d03dc2 100644 --- a/Controls/ButtonPropertyGridControlFactory.cs +++ b/Controls/ButtonPropertyGridControlFactory.cs @@ -15,7 +15,7 @@ public class ButtonPropertyGridControlFactory : IControlFactory { public FrameworkElement? TryCreateControl(PropertyItem property, PropertyControlFactoryOptions options) { - if (property.Descriptor.GetFirstAttributeOrDefault() is not {} target) + if (property.Descriptor.GetFirstAttributeOrDefault() is not { } target) return null; var button = new Button(); diff --git a/Controls/CharacterEditor.xaml.cs b/Controls/CharacterEditor.xaml.cs index a05e6bf..16fa259 100644 --- a/Controls/CharacterEditor.xaml.cs +++ b/Controls/CharacterEditor.xaml.cs @@ -1,16 +1,12 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; +using ColorPicker.Models; +using SpaceEditor.Data; +using SpaceEditor.Data.GameLinks; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Media.Imaging; -using ColorPicker.Models; -using SpaceEditor.Data; -using SpaceEditor.Data.GameLinks; namespace SpaceEditor.Controls; @@ -26,7 +22,7 @@ public partial class CharacterEditor : UserControl // Non-null value signifies initialization is done public volatile NotifyableColor? Color; - + private readonly GameLink GameLink; private bool IsConnectedToGameImpl; @@ -53,7 +49,7 @@ private bool IsConnectedToGame public CharacterEditor(GameProxy game, GameLink gameLink) { this.GameLink = gameLink; - + InitializeComponent(); this.IsConnectedToGame = false; @@ -65,8 +61,8 @@ public CharacterEditor(GameProxy game, GameLink gameLink) this.SecondaryColor.SecondaryColor = System.Windows.Media.Color.FromRgb ( - 0xff, - 0x2a, + 0xff, + 0x2a, 0x1f ); @@ -74,7 +70,7 @@ public CharacterEditor(GameProxy game, GameLink gameLink) { this.Albedo = Read("Resources/CharacterEditor/Albedo.png"); this.Mask = Read("Resources/CharacterEditor/Mask.png"); - this.OutputMemoryPool = (Argb32[,]) this.Albedo.Clone(); + this.OutputMemoryPool = (Argb32[,])this.Albedo.Clone(); var dpi = VisualTreeHelper.GetDpi(this); var previewBitmap = this.Dispatcher.Invoke(() => @@ -88,7 +84,7 @@ public CharacterEditor(GameProxy game, GameLink gameLink) PixelFormats.Bgra32, null ); - + Write(previewBitmap, this.Albedo); return previewBitmap; }); @@ -122,7 +118,7 @@ private void UpdatePreview() async Task Impl() { - Run: + Run: this.NeedsUpdate = false; var suitColor = new Argb32 @@ -142,22 +138,22 @@ async Task Impl() var w = albedo.GetLength(1); var h = albedo.GetLength(0); for (int x = 0; x < w; x++) - for (int y = 0; y < h; y++) - { - var c1 = albedo[y, x]; - var c2 = mask[y, x]; - - if (c2 with {A = default } != default) + for (int y = 0; y < h; y++) { - c1 = MultiplyColors(c1, MultiplyColors(c2, suitColor)); - } + var c1 = albedo[y, x]; + var c2 = mask[y, x]; - output[y, x] = c1; - } + if (c2 with { A = default } != default) + { + c1 = MultiplyColors(c1, MultiplyColors(c2, suitColor)); + } + + output[y, x] = c1; + } return output; }); - + await SendToGameIfNeeded(); Write(this.PreviewBitmap, await bitmapCompute); @@ -173,10 +169,10 @@ public static Argb32 MultiplyColors(Argb32 c1, Argb32 c2) { return new() { - B = (byte) (Math.Round((c1.R / 255.0) * (c2.R / 255.0) * 255.0)), - G = (byte) (Math.Round((c1.G / 255.0) * (c2.G / 255.0) * 255.0)), - R = (byte) (Math.Round((c1.B / 255.0) * (c2.B / 255.0) * 255.0)), - A = (byte) (Math.Round((c1.A / 255.0) * (c2.A / 255.0) * 255.0)), + B = (byte)(Math.Round((c1.R / 255.0) * (c2.R / 255.0) * 255.0)), + G = (byte)(Math.Round((c1.G / 255.0) * (c2.G / 255.0) * 255.0)), + R = (byte)(Math.Round((c1.B / 255.0) * (c2.B / 255.0) * 255.0)), + A = (byte)(Math.Round((c1.A / 255.0) * (c2.A / 255.0) * 255.0)), }; } @@ -272,10 +268,10 @@ private async void ConnectToGame(object sender, RoutedEventArgs _) this.LastConnectionTask = connectionTask; PrintStatus(success: "Testing connection ..."); - - var button = (Button) sender; + + var button = (Button)sender; button.IsEnabled = false; - + Exception? connectionError = null; try { @@ -294,7 +290,7 @@ private async void ConnectToGame(object sender, RoutedEventArgs _) // Throw if anything is stored inside await connectionTask; } - catch(Exception e) + catch (Exception e) { connectionError = e; } @@ -317,19 +313,19 @@ private async void DisconnectFromGame(object _, RoutedEventArgs __) this.LastConnectionTask = Task.CompletedTask; this.IsConnectedToGame = false; } - + private async Task SendToGameIfNeeded() { if (this.Color is null) return; - + if (this.IsConnectedToGame == false) return; - uint colorInt = ((uint) byte.MaxValue << 24) | - ((uint) (byte) (this.Color.RGB_R) << 16) | - ((uint) (byte) (this.Color.RGB_G) << 8) | - ((uint) (byte) (this.Color.RGB_B) << 0); + uint colorInt = ((uint)byte.MaxValue << 24) | + ((uint)(byte)(this.Color.RGB_R) << 16) | + ((uint)(byte)(this.Color.RGB_G) << 8) | + ((uint)(byte)(this.Color.RGB_B) << 0); var error = await Task.Run(async () => { @@ -337,7 +333,7 @@ private async Task SendToGameIfNeeded() { await this.GameLink.PaintCharacters(colorInt); } - catch(Exception e) + catch (Exception e) { return e; } @@ -351,12 +347,12 @@ private async Task SendToGameIfNeeded() private void PrintStatus(Exception? error = null, string? success = null) { var message = success; - + if (error is not null) { - message = error.Message + - Environment.NewLine + - Environment.NewLine + + message = error.Message + + Environment.NewLine + + Environment.NewLine + error.ToString(); } diff --git a/Controls/ColourSlider.xaml.cs b/Controls/ColourSlider.xaml.cs index 6c5d7bc..f47f860 100644 --- a/Controls/ColourSlider.xaml.cs +++ b/Controls/ColourSlider.xaml.cs @@ -1,8 +1,5 @@ -using System; -using System.Threading; -using System.Windows; +using System.Windows; using System.Windows.Controls; -using System.Windows.Controls.Primitives; using System.Windows.Media; using System.Windows.Media.Imaging; @@ -58,7 +55,7 @@ public ColourSlider() public Color SelectedColour { - get { return (Color) this.GetValue(SelectedColoursProperty); } + get { return (Color)this.GetValue(SelectedColoursProperty); } set { this.SetValue(SelectedColoursProperty, value); } } @@ -76,7 +73,7 @@ protected override void OnRender(DrawingContext drawingContext) { if (this.CacheBitmap() == false) return; - + this.SetColour(this.SelectedColour); this.isFirstTime = false; } @@ -91,11 +88,11 @@ protected override void OnValueChanged(double oldValue, double newValue) { this.isValueUpdating = true; - if (this.colourGradient is {} bitmap) + if (this.colourGradient is { } bitmap) { // work out the track position based on the control's width double width = this.colourGradient.Width; - int position = (int) (((newValue - base.Minimum) / (base.Maximum - base.Minimum)) * width); + int position = (int)(((newValue - base.Minimum) / (base.Maximum - base.Minimum)) * width); this.SelectedColour = GetColour(bitmap, position); RaiseEvent(new(ColorChangedEvent, this)); @@ -160,10 +157,10 @@ private Color GetColour(BitmapSource bitmap, int position) { if (position >= bitmap.Width - 1) { - position = (int) bitmap.Width - 2; + position = (int)bitmap.Width - 2; } - CroppedBitmap cb = new CroppedBitmap(bitmap, new Int32Rect(position, (int) this.VisualBounds.Height / 2, 1, 1)); + CroppedBitmap cb = new CroppedBitmap(bitmap, new Int32Rect(position, (int)this.VisualBounds.Height / 2, 1, 1)); byte[] tricolour = new byte[4]; cb.CopyPixels(tricolour, 4, 0); @@ -178,7 +175,7 @@ private bool CacheBitmap() if (double.IsInfinity(bounds.Width) || double.IsInfinity(bounds.Height)) return false; - RenderTargetBitmap source = new RenderTargetBitmap((int) bounds.Width, (int) bounds.Height, 96, 96, PixelFormats.Pbgra32); + RenderTargetBitmap source = new RenderTargetBitmap((int)bounds.Width, (int)bounds.Height, 96, 96, PixelFormats.Pbgra32); DrawingVisual dv = new DrawingVisual(); @@ -195,8 +192,8 @@ private bool CacheBitmap() private static void SelectedColourChangedCallBack(DependencyObject property, DependencyPropertyChangedEventArgs args) { - ColourSlider colourSlider = (ColourSlider) property; - Color colour = (Color) args.NewValue; + ColourSlider colourSlider = (ColourSlider)property; + Color colour = (Color)args.NewValue; colourSlider.SetColour(colour); } @@ -215,7 +212,7 @@ public static double Distance(Color source, Color target) public static System.Drawing.Color ToDrawingColour(Color source) { - return System.Drawing.Color.FromArgb((int) source.R, (int) source.G, (int) source.B); + return System.Drawing.Color.FromArgb((int)source.R, (int)source.G, (int)source.B); } #endregion diff --git a/Controls/CompositePropertyGridControlFactory.cs b/Controls/CompositePropertyGridControlFactory.cs index ea39a8b..7a6e397 100644 --- a/Controls/CompositePropertyGridControlFactory.cs +++ b/Controls/CompositePropertyGridControlFactory.cs @@ -1,7 +1,4 @@ using PropertyTools.Wpf; -using System; -using System.Collections.Generic; -using System.Linq; using System.Windows; namespace SpaceEditor.Controls; @@ -22,9 +19,9 @@ public override FrameworkElement CreateControl(PropertyItem property, PropertyCo public virtual FrameworkElement? TryCreateControl(PropertyItem property, PropertyControlFactoryOptions options) { - foreach(var factory in this.Factories) + foreach (var factory in this.Factories) { - if (factory.TryCreateControl(property, options) is {} control) + if (factory.TryCreateControl(property, options) is { } control) { return control; } diff --git a/Controls/FlatGroupingDataGridOperator.cs b/Controls/FlatGroupingDataGridOperator.cs index 8852f73..08e045f 100644 --- a/Controls/FlatGroupingDataGridOperator.cs +++ b/Controls/FlatGroupingDataGridOperator.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using PropertyTools.Wpf; +using PropertyTools.Wpf; namespace SpaceEditor.Controls; diff --git a/Controls/InputIdControlsFactory.cs b/Controls/InputIdControlsFactory.cs index 816f625..484baa3 100644 --- a/Controls/InputIdControlsFactory.cs +++ b/Controls/InputIdControlsFactory.cs @@ -1,9 +1,9 @@ -using System.Collections; +using PropertyTools.Wpf; +using SpaceEditor.Data; +using System.Collections; using System.Windows; using System.Windows.Controls; using System.Windows.Data; -using PropertyTools.Wpf; -using SpaceEditor.Data; namespace SpaceEditor.Controls; @@ -35,9 +35,9 @@ public class InputIdControlsFactory : IControlFactory { var inputIds = property.Descriptor.ComponentType.Name switch { - {} s when s.StartsWith("Digital") => this.InputIds.Digitals, - {} s when s.StartsWith("Analog") => this.InputIds.Analogs, - {} s when s.StartsWith("Pointer") => this.InputIds.Pointers, + { } s when s.StartsWith("Digital") => this.InputIds.Digitals, + { } s when s.StartsWith("Analog") => this.InputIds.Analogs, + { } s when s.StartsWith("Pointer") => this.InputIds.Pointers, }; var keyValueInputs = inputIds.Select(x => diff --git a/Controls/InputsEditor.xaml.cs b/Controls/InputsEditor.xaml.cs index 902ef16..d026b4c 100644 --- a/Controls/InputsEditor.xaml.cs +++ b/Controls/InputsEditor.xaml.cs @@ -1,12 +1,9 @@ -using System; -using System.Collections.Generic; +using ReflectionMagic; +using SpaceEditor.Data; using System.ComponentModel; -using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Data; -using ReflectionMagic; -using SpaceEditor.Data; namespace SpaceEditor.Controls; @@ -46,7 +43,7 @@ private void OnDataContextChanged(object sender, DependencyPropertyChangedEventA var cv = CollectionViewSource.GetDefaultView(vm.Actions.Actions.Select(x => x.Value).ToList()); cv.Filter = x => { - var candidate = ((InputActions.InputActionInfo) x).DisplayName; + var candidate = ((InputActions.InputActionInfo)x).DisplayName; return candidate.Contains(this.InputActionsSearchString, StringComparison.InvariantCultureIgnoreCase); }; @@ -62,7 +59,7 @@ private static void OnInputActionsSearchStringChanged(DependencyObject d, Depend private void OnInputActionSelected(object sender, SelectionChangedEventArgs e) { - var selected = (InputActions.InputActionInfo?) this.ActionList.SelectedValue; + var selected = (InputActions.InputActionInfo?)this.ActionList.SelectedValue; if (selected is null) goto Nothing; @@ -81,7 +78,7 @@ private void OnInputActionSelected(object sender, SelectionChangedEventArgs e) this.BindingsEditor.Visibility = Visibility.Visible; return; - Nothing: + Nothing: this.BindingsEditor.DataContext = null; this.BindingsEditor.ReflectedItems = null; this.BindingsEditor.Visibility = Visibility.Hidden; diff --git a/Controls/KeyBindsEditor.xaml.cs b/Controls/KeyBindsEditor.xaml.cs index 045f0b6..b5ffa43 100644 --- a/Controls/KeyBindsEditor.xaml.cs +++ b/Controls/KeyBindsEditor.xaml.cs @@ -1,15 +1,11 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; +using ObservableCollections; +using SpaceEditor.Data; +using SpaceEditor.Rocks; using System.Collections.Specialized; using System.ComponentModel; using System.IO; -using System.Linq; using System.Windows; using System.Windows.Controls; -using ObservableCollections; -using SpaceEditor.Data; -using SpaceEditor.Rocks; namespace SpaceEditor.Controls; @@ -19,9 +15,9 @@ namespace SpaceEditor.Controls; public partial class KeyBindsEditor : UserControl { private const string CurrentPreset = "Current"; - + public GameProxy Game { get; } - + private Settings Settings => Settings.Default; public ObservableDictionary Presets { get; } = new(); public INotifyCollectionChanged PresetsView => this.Presets.ToNotifyCollectionChanged(); @@ -33,12 +29,12 @@ public KeyBindsEditor(GameProxy game) var mappingsFile = GameFacts.GetMappingFile(this.Game.BaseGamePath); var content = File.ReadAllText(mappingsFile); this.Presets.Add(CurrentPreset, content); - + foreach (var (key, dataString) in this.Settings.NamedPresets) { this.Presets[key] = dataString; } - + InitializeComponent(); } @@ -57,7 +53,7 @@ private void OnPresetChanged(object sender, SelectionChangedEventArgs e) private void OnPresetChanged2(object? sender, EventArgs e) { - var key = (string?) this.PresetsCombo.SelectedValue; + var key = (string?)this.PresetsCombo.SelectedValue; if (key is null) return; @@ -119,7 +115,7 @@ private void OnApplyClicked(object sender, RoutedEventArgs e) private void OnCurrentVM(Action action) { - var vm = (PresetVM?) this.InputsEditorControl.DataContext; + var vm = (PresetVM?)this.InputsEditorControl.DataContext; if (vm is null) return; diff --git a/Controls/ModelViewport.xaml.cs b/Controls/ModelViewport.xaml.cs index d56aad9..5c5bde0 100644 --- a/Controls/ModelViewport.xaml.cs +++ b/Controls/ModelViewport.xaml.cs @@ -1,9 +1,7 @@ using g4; using SpaceEditor.Data; using SpaceEditor.Rocks; -using System; using System.ComponentModel; -using System.Linq; using System.Numerics; using System.Windows; using System.Windows.Controls; @@ -33,11 +31,11 @@ public Color Color get => this.ColorImpl; set => SetField(ref this.ColorImpl, value); } - + public float Opacity { - get => (float) this.Color.A / byte.MaxValue; - set => this.Color = this.Color with {A = (byte)(value * byte.MaxValue) }; + get => (float)this.Color.A / byte.MaxValue; + set => this.Color = this.Color with { A = (byte)(value * byte.MaxValue) }; } } @@ -59,7 +57,7 @@ public IDisposable AddModel(ModelData model) ( m.Triangles().SelectMany(t => { - return new[]{ t.a, t.b, t.c }; + return new[] { t.a, t.b, t.c }; }) ); @@ -80,7 +78,7 @@ public IDisposable AddModel(ModelData model) Geometry = mesh, Transform = Transform3D.Identity }; - + var renderModel = new ModelVisual3D { Content = geometry @@ -138,7 +136,7 @@ private void OnMouseMove(object sender, MouseEventArgs e) return; var cameraPosition = this.Camera.Position; - UpdateObitCamera(default, ref cameraPosition, new(-(float) diff.X, (float) -diff.Y), 0.03f); + UpdateObitCamera(default, ref cameraPosition, new(-(float)diff.X, (float)-diff.Y), 0.03f); SetCameraParameters(cameraPosition); } diff --git a/Controls/PCUUnlocker.xaml.cs b/Controls/PCUUnlocker.xaml.cs index ab359e4..88c7051 100644 --- a/Controls/PCUUnlocker.xaml.cs +++ b/Controls/PCUUnlocker.xaml.cs @@ -1,12 +1,8 @@ -using System; -using System.Collections.Generic; +using SpaceEditor.Data; using System.IO; -using System.Linq; -using System.Text; using System.Text.RegularExpressions; using System.Windows; using System.Windows.Controls; -using SpaceEditor.Data; namespace SpaceEditor.Controls; @@ -53,7 +49,7 @@ private void UnlockPCU(object sender, RoutedEventArgs e) const string PlayergridPCU = "\"PlayerGridPCU\""; const string TargetCleanupLimit = "\"TargetCleanupLimit\""; const string ExecuteCleanupLimit = "\"ExecuteCleanupLimit\""; - + var gridSectionBegin = content.IndexOf(PlayergridPCU, StringComparison.InvariantCulture); if (gridSectionBegin < 0) return null; @@ -83,12 +79,12 @@ private void UnlockPCU(object sender, RoutedEventArgs e) Settings.Default.InvokeGameAction(() => { - foreach(var (file, newContent) in contents) + foreach (var (file, newContent) in contents) { File.WriteAllText(file, newContent); } }); - + bool UpdateFile(string fileName, Func updateFunction) { var filePath = GameFacts.TryFindTargetPath(this.Game.ContentPath, [], fileName); @@ -98,7 +94,7 @@ bool UpdateFile(string fileName, Func updateFunction) } var content = contents.GetValueOrDefault(filePath) ?? File.ReadAllText(filePath); - + var newContent = updateFunction(content); if (newContent is null) return false; diff --git a/Controls/ReflectedCollection.xaml.cs b/Controls/ReflectedCollection.xaml.cs index f73877f..4716212 100644 --- a/Controls/ReflectedCollection.xaml.cs +++ b/Controls/ReflectedCollection.xaml.cs @@ -1,21 +1,8 @@ -using System; +using SpaceEditor.Rocks; using System.Collections; -using System.Collections.Generic; using System.ComponentModel; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; -using System.Windows.Data; -using System.Windows.Documents; -using System.Windows.Input; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using System.Windows.Navigation; -using System.Windows.Shapes; -using SpaceEditor.Data; -using SpaceEditor.Rocks; namespace SpaceEditor.Controls; @@ -59,9 +46,9 @@ public ReflectedCollection() private void RemoveBinding(object sender, RoutedEventArgs e) { - var v = (FrameworkElement) sender; + var v = (FrameworkElement)sender; var current = v.DataContext!; - + UpdateCollection(x => { x.Remove(current); @@ -70,7 +57,7 @@ private void RemoveBinding(object sender, RoutedEventArgs e) private void AddElement(object sender, RoutedEventArgs e) { - var selectedType = (Type?) this.NewElementTypes.SelectedValue; + var selectedType = (Type?)this.NewElementTypes.SelectedValue; if (selectedType is null) return; @@ -84,7 +71,7 @@ private void UpdateCollection(Action update) { if (this.ReflectedItems is ICollectionView cv) { - update((IList) cv.SourceCollection); + update((IList)cv.SourceCollection); cv.Refresh(); return; } diff --git a/Controls/ReflectedObject.xaml.cs b/Controls/ReflectedObject.xaml.cs index d241d3c..3e6bfc4 100644 --- a/Controls/ReflectedObject.xaml.cs +++ b/Controls/ReflectedObject.xaml.cs @@ -1,11 +1,7 @@ -using System; +using SpaceEditor.Rocks; using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; using System.Windows; using System.Windows.Controls; -using SpaceEditor.Rocks; namespace SpaceEditor.Controls; @@ -32,13 +28,13 @@ public partial class ReflectedObject : UserControl public object ReflectedInstance { - get { return (object) GetValue(ReflectedInstanceProperty); } + get { return (object)GetValue(ReflectedInstanceProperty); } set { SetValue(ReflectedInstanceProperty, value); } } public IEnumerable NewObjectTypeCandidates { - get { return (IEnumerable) GetValue(NewObjectTypeCandidatesProperty); } + get { return (IEnumerable)GetValue(NewObjectTypeCandidatesProperty); } set { SetValue(NewObjectTypeCandidatesProperty, value); } } @@ -52,7 +48,7 @@ private void OnNewTypeSelected(object sender, SelectionChangedEventArgs e) if (e.AddedItems.Count == 0) return; - var type = ((KeyValuePair) e.AddedItems[0]!).Value; + var type = ((KeyValuePair)e.AddedItems[0]!).Value; this.ReflectedInstance = type.AllocateObjectBuilder(); } } \ No newline at end of file diff --git a/Data/GameFacts.cs b/Data/GameFacts.cs index 63f7e1e..c62f005 100644 --- a/Data/GameFacts.cs +++ b/Data/GameFacts.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; +using System.IO; namespace SpaceEditor.Data; @@ -85,14 +82,14 @@ public static string? TryFindTargetPath var testDir = Path.Combine(baseDirectory, currentDirToSearchFor); if (Directory.Exists(testDir)) { - if (TryFindTargetPath(testDir, subDirectories[1..], targetFile, remainingSearchDepth) is {} result) + if (TryFindTargetPath(testDir, subDirectories[1..], targetFile, remainingSearchDepth) is { } result) return result; } } foreach (var dir in Directory.EnumerateDirectories(baseDirectory)) { - if (TryFindTargetPath(dir, subDirectories, targetFile, remainingSearchDepth) is {} result) + if (TryFindTargetPath(dir, subDirectories, targetFile, remainingSearchDepth) is { } result) return result; } diff --git a/Data/GameLinks/DbgShimResolver.cs b/Data/GameLinks/DbgShimResolver.cs index 464b6f4..0bb604e 100644 --- a/Data/GameLinks/DbgShimResolver.cs +++ b/Data/GameLinks/DbgShimResolver.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; +using System.IO; using System.Runtime.InteropServices; namespace SpaceEditor.Data.GameLinks; diff --git a/Data/GameLinks/DebuggerCallbacks.cs b/Data/GameLinks/DebuggerCallbacks.cs index 8d477f9..5e4bb0d 100644 --- a/Data/GameLinks/DebuggerCallbacks.cs +++ b/Data/GameLinks/DebuggerCallbacks.cs @@ -72,229 +72,229 @@ bool Impl(T e) if (typeof(T) == typeof(BreakpointCorDebugManagedCallbackEventArgs)) { EventHandler handler = null!; - handler = (_, e) => { if (Impl((T) (object) e)) instance.OnBreakpoint -= handler; }; + handler = (_, e) => { if (Impl((T)(object)e)) instance.OnBreakpoint -= handler; }; instance.OnBreakpoint += handler; } else if (typeof(T) == typeof(StepCompleteCorDebugManagedCallbackEventArgs)) { EventHandler handler = null!; - handler = (_, e) => { if (Impl((T) (object) e)) instance.OnStepComplete -= handler; }; + handler = (_, e) => { if (Impl((T)(object)e)) instance.OnStepComplete -= handler; }; instance.OnStepComplete += handler; } else if (typeof(T) == typeof(BreakCorDebugManagedCallbackEventArgs)) { EventHandler handler = null!; - handler = (_, e) => { if (Impl((T) (object) e)) instance.OnBreak -= handler; }; + handler = (_, e) => { if (Impl((T)(object)e)) instance.OnBreak -= handler; }; instance.OnBreak += handler; } else if (typeof(T) == typeof(ExceptionCorDebugManagedCallbackEventArgs)) { EventHandler handler = null!; - handler = (_, e) => { if (Impl((T) (object) e)) instance.OnException -= handler; }; + handler = (_, e) => { if (Impl((T)(object)e)) instance.OnException -= handler; }; instance.OnException += handler; } else if (typeof(T) == typeof(EvalCompleteCorDebugManagedCallbackEventArgs)) { EventHandler handler = null!; - handler = (_, e) => { if (Impl((T) (object) e)) instance.OnEvalComplete -= handler; }; + handler = (_, e) => { if (Impl((T)(object)e)) instance.OnEvalComplete -= handler; }; instance.OnEvalComplete += handler; } else if (typeof(T) == typeof(EvalExceptionCorDebugManagedCallbackEventArgs)) { EventHandler handler = null!; - handler = (_, e) => { if (Impl((T) (object) e)) instance.OnEvalException -= handler; }; + handler = (_, e) => { if (Impl((T)(object)e)) instance.OnEvalException -= handler; }; instance.OnEvalException += handler; } else if (typeof(T) == typeof(CreateProcessCorDebugManagedCallbackEventArgs)) { EventHandler handler = null!; - handler = (_, e) => { if (Impl((T) (object) e)) instance.OnCreateProcess -= handler; }; + handler = (_, e) => { if (Impl((T)(object)e)) instance.OnCreateProcess -= handler; }; instance.OnCreateProcess += handler; } else if (typeof(T) == typeof(ExitProcessCorDebugManagedCallbackEventArgs)) { EventHandler handler = null!; - handler = (_, e) => { if (Impl((T) (object) e)) instance.OnExitProcess -= handler; }; + handler = (_, e) => { if (Impl((T)(object)e)) instance.OnExitProcess -= handler; }; instance.OnExitProcess += handler; } else if (typeof(T) == typeof(CreateThreadCorDebugManagedCallbackEventArgs)) { EventHandler handler = null!; - handler = (_, e) => { if (Impl((T) (object) e)) instance.OnCreateThread -= handler; }; + handler = (_, e) => { if (Impl((T)(object)e)) instance.OnCreateThread -= handler; }; instance.OnCreateThread += handler; } else if (typeof(T) == typeof(ExitThreadCorDebugManagedCallbackEventArgs)) { EventHandler handler = null!; - handler = (_, e) => { if (Impl((T) (object) e)) instance.OnExitThread -= handler; }; + handler = (_, e) => { if (Impl((T)(object)e)) instance.OnExitThread -= handler; }; instance.OnExitThread += handler; } else if (typeof(T) == typeof(LoadModuleCorDebugManagedCallbackEventArgs)) { EventHandler handler = null!; - handler = (_, e) => { if (Impl((T) (object) e)) instance.OnLoadModule -= handler; }; + handler = (_, e) => { if (Impl((T)(object)e)) instance.OnLoadModule -= handler; }; instance.OnLoadModule += handler; } else if (typeof(T) == typeof(UnloadModuleCorDebugManagedCallbackEventArgs)) { EventHandler handler = null!; - handler = (_, e) => { if (Impl((T) (object) e)) instance.OnUnloadModule -= handler; }; + handler = (_, e) => { if (Impl((T)(object)e)) instance.OnUnloadModule -= handler; }; instance.OnUnloadModule += handler; } else if (typeof(T) == typeof(LoadClassCorDebugManagedCallbackEventArgs)) { EventHandler handler = null!; - handler = (_, e) => { if (Impl((T) (object) e)) instance.OnLoadClass -= handler; }; + handler = (_, e) => { if (Impl((T)(object)e)) instance.OnLoadClass -= handler; }; instance.OnLoadClass += handler; } else if (typeof(T) == typeof(UnloadClassCorDebugManagedCallbackEventArgs)) { EventHandler handler = null!; - handler = (_, e) => { if (Impl((T) (object) e)) instance.OnUnloadClass -= handler; }; + handler = (_, e) => { if (Impl((T)(object)e)) instance.OnUnloadClass -= handler; }; instance.OnUnloadClass += handler; } else if (typeof(T) == typeof(DebuggerErrorCorDebugManagedCallbackEventArgs)) { EventHandler handler = null!; - handler = (_, e) => { if (Impl((T) (object) e)) instance.OnDebuggerError -= handler; }; + handler = (_, e) => { if (Impl((T)(object)e)) instance.OnDebuggerError -= handler; }; instance.OnDebuggerError += handler; } else if (typeof(T) == typeof(LogMessageCorDebugManagedCallbackEventArgs)) { EventHandler handler = null!; - handler = (_, e) => { if (Impl((T) (object) e)) instance.OnLogMessage -= handler; }; + handler = (_, e) => { if (Impl((T)(object)e)) instance.OnLogMessage -= handler; }; instance.OnLogMessage += handler; } else if (typeof(T) == typeof(LogSwitchCorDebugManagedCallbackEventArgs)) { EventHandler handler = null!; - handler = (_, e) => { if (Impl((T) (object) e)) instance.OnLogSwitch -= handler; }; + handler = (_, e) => { if (Impl((T)(object)e)) instance.OnLogSwitch -= handler; }; instance.OnLogSwitch += handler; } else if (typeof(T) == typeof(CreateAppDomainCorDebugManagedCallbackEventArgs)) { EventHandler handler = null!; - handler = (_, e) => { if (Impl((T) (object) e)) instance.OnCreateAppDomain -= handler; }; + handler = (_, e) => { if (Impl((T)(object)e)) instance.OnCreateAppDomain -= handler; }; instance.OnCreateAppDomain += handler; } else if (typeof(T) == typeof(ExitAppDomainCorDebugManagedCallbackEventArgs)) { EventHandler handler = null!; - handler = (_, e) => { if (Impl((T) (object) e)) instance.OnExitAppDomain -= handler; }; + handler = (_, e) => { if (Impl((T)(object)e)) instance.OnExitAppDomain -= handler; }; instance.OnExitAppDomain += handler; } else if (typeof(T) == typeof(LoadAssemblyCorDebugManagedCallbackEventArgs)) { EventHandler handler = null!; - handler = (_, e) => { if (Impl((T) (object) e)) instance.OnLoadAssembly -= handler; }; + handler = (_, e) => { if (Impl((T)(object)e)) instance.OnLoadAssembly -= handler; }; instance.OnLoadAssembly += handler; } else if (typeof(T) == typeof(UnloadAssemblyCorDebugManagedCallbackEventArgs)) { EventHandler handler = null!; - handler = (_, e) => { if (Impl((T) (object) e)) instance.OnUnloadAssembly -= handler; }; + handler = (_, e) => { if (Impl((T)(object)e)) instance.OnUnloadAssembly -= handler; }; instance.OnUnloadAssembly += handler; } else if (typeof(T) == typeof(ControlCTrapCorDebugManagedCallbackEventArgs)) { EventHandler handler = null!; - handler = (_, e) => { if (Impl((T) (object) e)) instance.OnControlCTrap -= handler; }; + handler = (_, e) => { if (Impl((T)(object)e)) instance.OnControlCTrap -= handler; }; instance.OnControlCTrap += handler; } else if (typeof(T) == typeof(NameChangeCorDebugManagedCallbackEventArgs)) { EventHandler handler = null!; - handler = (_, e) => { if (Impl((T) (object) e)) instance.OnNameChange -= handler; }; + handler = (_, e) => { if (Impl((T)(object)e)) instance.OnNameChange -= handler; }; instance.OnNameChange += handler; } else if (typeof(T) == typeof(UpdateModuleSymbolsCorDebugManagedCallbackEventArgs)) { EventHandler handler = null!; - handler = (_, e) => { if (Impl((T) (object) e)) instance.OnUpdateModuleSymbols -= handler; }; + handler = (_, e) => { if (Impl((T)(object)e)) instance.OnUpdateModuleSymbols -= handler; }; instance.OnUpdateModuleSymbols += handler; } else if (typeof(T) == typeof(EditAndContinueRemapCorDebugManagedCallbackEventArgs)) { EventHandler handler = null!; - handler = (_, e) => { if (Impl((T) (object) e)) instance.OnEditAndContinueRemap -= handler; }; + handler = (_, e) => { if (Impl((T)(object)e)) instance.OnEditAndContinueRemap -= handler; }; instance.OnEditAndContinueRemap += handler; } else if (typeof(T) == typeof(BreakpointSetErrorCorDebugManagedCallbackEventArgs)) { EventHandler handler = null!; - handler = (_, e) => { if (Impl((T) (object) e)) instance.OnBreakpointSetError -= handler; }; + handler = (_, e) => { if (Impl((T)(object)e)) instance.OnBreakpointSetError -= handler; }; instance.OnBreakpointSetError += handler; } else if (typeof(T) == typeof(FunctionRemapOpportunityCorDebugManagedCallbackEventArgs)) { EventHandler handler = null!; - handler = (_, e) => { if (Impl((T) (object) e)) instance.OnFunctionRemapOpportunity -= handler; }; + handler = (_, e) => { if (Impl((T)(object)e)) instance.OnFunctionRemapOpportunity -= handler; }; instance.OnFunctionRemapOpportunity += handler; } else if (typeof(T) == typeof(CreateConnectionCorDebugManagedCallbackEventArgs)) { EventHandler handler = null!; - handler = (_, e) => { if (Impl((T) (object) e)) instance.OnCreateConnection -= handler; }; + handler = (_, e) => { if (Impl((T)(object)e)) instance.OnCreateConnection -= handler; }; instance.OnCreateConnection += handler; } else if (typeof(T) == typeof(ChangeConnectionCorDebugManagedCallbackEventArgs)) { EventHandler handler = null!; - handler = (_, e) => { if (Impl((T) (object) e)) instance.OnChangeConnection -= handler; }; + handler = (_, e) => { if (Impl((T)(object)e)) instance.OnChangeConnection -= handler; }; instance.OnChangeConnection += handler; } else if (typeof(T) == typeof(DestroyConnectionCorDebugManagedCallbackEventArgs)) { EventHandler handler = null!; - handler = (_, e) => { if (Impl((T) (object) e)) instance.OnDestroyConnection -= handler; }; + handler = (_, e) => { if (Impl((T)(object)e)) instance.OnDestroyConnection -= handler; }; instance.OnDestroyConnection += handler; } else if (typeof(T) == typeof(Exception2CorDebugManagedCallbackEventArgs)) { EventHandler handler = null!; - handler = (_, e) => { if (Impl((T) (object) e)) instance.OnException2 -= handler; }; + handler = (_, e) => { if (Impl((T)(object)e)) instance.OnException2 -= handler; }; instance.OnException2 += handler; } else if (typeof(T) == typeof(ExceptionUnwindCorDebugManagedCallbackEventArgs)) { EventHandler handler = null!; - handler = (_, e) => { if (Impl((T) (object) e)) instance.OnExceptionUnwind -= handler; }; + handler = (_, e) => { if (Impl((T)(object)e)) instance.OnExceptionUnwind -= handler; }; instance.OnExceptionUnwind += handler; } else if (typeof(T) == typeof(FunctionRemapCompleteCorDebugManagedCallbackEventArgs)) { EventHandler handler = null!; - handler = (_, e) => { if (Impl((T) (object) e)) instance.OnFunctionRemapComplete -= handler; }; + handler = (_, e) => { if (Impl((T)(object)e)) instance.OnFunctionRemapComplete -= handler; }; instance.OnFunctionRemapComplete += handler; } else if (typeof(T) == typeof(MDANotificationCorDebugManagedCallbackEventArgs)) { EventHandler handler = null!; - handler = (_, e) => { if (Impl((T) (object) e)) instance.OnMDANotification -= handler; }; + handler = (_, e) => { if (Impl((T)(object)e)) instance.OnMDANotification -= handler; }; instance.OnMDANotification += handler; } else if (typeof(T) == typeof(CustomNotificationCorDebugManagedCallbackEventArgs)) { EventHandler handler = null!; - handler = (_, e) => { if (Impl((T) (object) e)) instance.OnCustomNotification -= handler; }; + handler = (_, e) => { if (Impl((T)(object)e)) instance.OnCustomNotification -= handler; }; instance.OnCustomNotification += handler; } else if (typeof(T) == typeof(BeforeGarbageCollectionCorDebugManagedCallbackEventArgs)) { EventHandler handler = null!; - handler = (_, e) => { if (Impl((T) (object) e)) instance.OnBeforeGarbageCollection -= handler; }; + handler = (_, e) => { if (Impl((T)(object)e)) instance.OnBeforeGarbageCollection -= handler; }; instance.OnBeforeGarbageCollection += handler; } else if (typeof(T) == typeof(AfterGarbageCollectionCorDebugManagedCallbackEventArgs)) { EventHandler handler = null!; - handler = (_, e) => { if (Impl((T) (object) e)) instance.OnAfterGarbageCollection -= handler; }; + handler = (_, e) => { if (Impl((T)(object)e)) instance.OnAfterGarbageCollection -= handler; }; instance.OnAfterGarbageCollection += handler; } else if (typeof(T) == typeof(DataBreakpointCorDebugManagedCallbackEventArgs)) { EventHandler handler = null!; - handler = (_, e) => { if (Impl((T) (object) e)) instance.OnDataBreakpoint -= handler; }; + handler = (_, e) => { if (Impl((T)(object)e)) instance.OnDataBreakpoint -= handler; }; instance.OnDataBreakpoint += handler; } diff --git a/Data/GameLinks/GameLink.Operation.cs b/Data/GameLinks/GameLink.Operation.cs index 20da939..1ff4600 100644 --- a/Data/GameLinks/GameLink.Operation.cs +++ b/Data/GameLinks/GameLink.Operation.cs @@ -1,10 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Runtime.CompilerServices; -using ClrDebug; +using ClrDebug; using SpaceEditor.Rocks; +using System.Runtime.CompilerServices; namespace SpaceEditor.Data.GameLinks; @@ -16,7 +12,7 @@ public class Operation public CorDebugProcess Process { get; init; } public CorDebugAppDomain Domain { get; init; } public CorDebugAssembly[] Assemblies { get; init; } - + public CorDebugManagedCallback Callbacks { get; init; } public (CorDebugModule Module, mdTypeDef Token) FindType(string typeName) @@ -49,7 +45,7 @@ public class Operation } } - Found: + Found: if (moduleMd!.TryFindTypeDefByName(typePart, parent, out parent).IsFail()) { throw new Exception($"Sub type {typePart} not found in {module!.Name}"); @@ -152,7 +148,7 @@ public async Task ExecutePreparedStep(CorDebugStepper stepper) var result = await ExecutePreparedEval(eval, expectResult: true); var handle = result as CorDebugHandleValue; - + CorDebugValue value = result; if (value is CorDebugReferenceValue ptr) { @@ -175,7 +171,7 @@ public async Task ExecutePreparedEval(CorDebugEval eval, bo var toString = FindFunction("System.Object", "ToString"); eval.CallFunction(toString.Raw, 1, [exception.Raw]); - + var extractedInfo = await Invoke(); if (extractedInfo) { @@ -208,7 +204,7 @@ public async Task ExecutePreparedEval(CorDebugEval eval, bo return null!; } - return (CorDebugHandleValue) eval.Result; + return (CorDebugHandleValue)eval.Result; async Task Invoke() { @@ -274,7 +270,7 @@ public unsafe T ReadPrimitiveValue(CorDebugValue value) } T store = default; - genericValue.GetValue((IntPtr) (void*) &store); + genericValue.GetValue((IntPtr)(void*)&store); return store; } @@ -282,8 +278,8 @@ public unsafe CorDebugValue CreatePrimitiveValue(CorDebugEval eval, T value) where T : unmanaged { var type = PrimitiveRuntimeTypeToCorType(typeof(T)); - var valueHandle = (CorDebugGenericValue) eval.CreateValue(type, null); - valueHandle.SetValue((IntPtr) (void*) &value); + var valueHandle = (CorDebugGenericValue)eval.CreateValue(type, null); + valueHandle.SetValue((IntPtr)(void*)&value); return valueHandle; } diff --git a/Data/GameLinks/GameLink.cs b/Data/GameLinks/GameLink.cs index 7884612..5d11674 100644 --- a/Data/GameLinks/GameLink.cs +++ b/Data/GameLinks/GameLink.cs @@ -1,8 +1,5 @@ using ClrDebug; -using SpaceEditor.Controls; -using System; using System.Diagnostics; -using System.Linq; using System.Runtime.InteropServices; namespace SpaceEditor.Data.GameLinks; @@ -10,7 +7,7 @@ namespace SpaceEditor.Data.GameLinks; public partial class GameLink : IAsyncDisposable { public GameProxy Game { get; } - + private readonly SemaphoreSlim SyncLock = new(1, 1); private readonly DbgShim DbgShim; @@ -54,7 +51,7 @@ public async Task InvokeOperation(Func invocation) { throw new Exception($"Could not find CLR"); } - + //Version String is a comma delimited value containing dbiVersion, pidDebuggee, hmodTargetCLR var versionStr = this.DbgShim.CreateVersionStringFromModule(processId, clrs[0].Path); @@ -125,7 +122,7 @@ public Task InvokeOnMainThread(Func invocation) // Session.Update is large enough to not get inlined, but it's not guaranteed which Scene it will hit on // var engineUpdateMethod = op.FindFunction("Keen.VRage.Core.VRageCore", "Update"); // thread = await op.CatchThreadInFunction(engineUpdateMethod); - + var sessionUpdateMethod = op.FindFunction("Keen.VRage.Core.Game.Systems.Session", "Update"); thread = await op.CatchThreadInFunction(sessionUpdateMethod); @@ -163,17 +160,17 @@ public Task InvokeOnComponents(string typeName, Func var entities = op.ReadField(session, "_activeEntities"); var entityType = entities.ExactType.FirstTypeParameter; - + var toArrayMethod = op.FindFunction("System.Linq.Enumerable", "ToArray"); - + var eval = thread.CreateEval(); eval.CallParameterizedFunction(toArrayMethod.Raw, 1, [entityType.Raw], 1, [entities.Raw]); var entitiesAsArrayHandle = await op.ExecutePreparedEval(eval, expectResult: true); try { - var entitiesAsArray = (CorDebugArrayValue) entitiesAsArrayHandle.Dereference(); + var entitiesAsArray = (CorDebugArrayValue)entitiesAsArrayHandle.Dereference(); var entitiesCount = entitiesAsArray.Count; - + List componentHandles = new(); try { @@ -181,7 +178,7 @@ public Task InvokeOnComponents(string typeName, Func().Dereference(); var entityComponentsImmutableArray = op.ReadField(entity, "Components"); - var entityComponents = (CorDebugArrayValue) op.ReadField(entityComponentsImmutableArray, "array").As().Dereference(); + var entityComponents = (CorDebugArrayValue)op.ReadField(entityComponentsImmutableArray, "array").As().Dereference(); CorDebugValue? componentHit = null; var cc = entityComponents.Count; @@ -237,11 +234,11 @@ public Task PaintCharacters(uint color) var eval = thread.CreateEval(); var colorValue = op.CreatePrimitiveValue(eval, color); eval.CallFunction(fromARGBMethod.Raw, 1, [colorValue.Raw]); - + var colorHandle = await op.ExecutePreparedEval(eval, expectResult: true); try { - foreach(var characterRef in characters) + foreach (var characterRef in characters) { eval.CallFunction(suitColorSetter.Raw, 2, [characterRef.Raw, colorHandle.Raw]); await op.ExecutePreparedEval(eval); diff --git a/Data/GameProxy.cs b/Data/GameProxy.cs index 55e6c48..0d8279c 100644 --- a/Data/GameProxy.cs +++ b/Data/GameProxy.cs @@ -1,14 +1,10 @@ -using System; -using System.Collections; -using System.Collections.Generic; +using Castle.DynamicProxy; +using ReflectionMagic; +using SpaceEditor.Rocks; using System.Diagnostics; using System.IO; -using System.Linq; using System.Reflection; using System.Text; -using Castle.DynamicProxy; -using ReflectionMagic; -using SpaceEditor.Rocks; namespace SpaceEditor.Data; @@ -33,7 +29,7 @@ public class InputActionInfo public InputActionInfo? TryGetInputActionInfo(object inputActionDefinitionStub) { var id = inputActionDefinitionStub.AsDynamic().Guid; - return TryGetInputActionInfo((Guid) id); + return TryGetInputActionInfo((Guid)id); } } @@ -98,10 +94,17 @@ public class GameProxy public string BinsPath { get; } public Assembly MainAssembly { get; } - + public AsyncLazy InputIds { get; } public AsyncLazy InputActions { get; } + // Pre-cached reflection types to eliminate massive loop bottlenecks + private readonly Type _customSerializationContextType; + private readonly Type _serializationContextType; + private readonly Type _serializationHelperType; + private readonly object _jsonFormatEnumValue; + private readonly MethodInfo _serializeAbstractMethod; + public GameProxy(string baseGamePath) { this.BaseGamePath = baseGamePath; @@ -111,10 +114,16 @@ public GameProxy(string baseGamePath) var se2 = ReflectionRocks.GetLib(this.BinsPath, GameFacts.MainDll); this.MainAssembly = se2; - var st = FindType("Keen.VRage.Library.Utils.Singleton"); + var st = FindType("Keen.VRage.Library.Utils.Singleton"); var mdt = FindType("Keen.VRage.Library.Reflection.MetadataManager"); var md = st.AsDynamicType().GetInstance(mdt); - md.PushContext(new[]{se2}); + md.PushContext(new[] { se2 }); + + _customSerializationContextType = FindType("CustomSerializationContext"); + _serializationContextType = FindType("SerializationContext"); + _serializationHelperType = FindType("SerializationHelper"); + _jsonFormatEnumValue = Enum.Parse(FindType("SerializerFormat"), "Json"); + _serializeAbstractMethod = _serializationHelperType.GetMethod("SerializeAbstract")!.MakeGenericMethod(typeof(object)); this.InputIds = new(LoadInputIds); this.InputActions = new(LoadInputActions); @@ -128,31 +137,28 @@ public Type FindType(string typeName) public dynamic DeserializeFile(string filePath, params object[] services) { - using var fs = new FileStream(filePath, FileMode.Open); + using var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read); return DeserializeObject(fs, services); } public dynamic DeserializeObject(Stream content, params object[] services) { - var typedServices = Array.CreateInstance(FindType("CustomSerializationContext"), services.Length); + var typedServices = Array.CreateInstance(_customSerializationContextType, services.Length); Array.Copy(services, typedServices, services.Length); - var format = Enum.Parse(FindType("SerializerFormat"), "Json"); - - using var sc = (IDisposable) Activator.CreateInstance(FindType("SerializationContext"), content, "NoName.txt", typedServices)!; - return FindType("SerializationHelper").AsDynamicType().DeserializeAbstract(sc, format); + using var sc = (IDisposable)Activator.CreateInstance(_serializationContextType, content, "NoName.txt", typedServices)!; + return _serializationHelperType.AsDynamicType().DeserializeAbstract(sc, _jsonFormatEnumValue); } public string SerializeObject(object instance, params object[] services) { - var typedServices = Array.CreateInstance(FindType("CustomSerializationContext"), services.Length); + var typedServices = Array.CreateInstance(_customSerializationContextType, services.Length); Array.Copy(services, typedServices, services.Length); - var format = Enum.Parse(FindType("SerializerFormat"), "Json"); - using var data = new MemoryStream(); - using var sc = (IDisposable) Activator.CreateInstance(FindType("SerializationContext"), data, "NoName.txt", typedServices)!; - FindType("SerializationHelper").GetMethod("SerializeAbstract")!.MakeGenericMethod(typeof(object)).Invoke(null, [sc, instance, format]); + using var sc = (IDisposable)Activator.CreateInstance(_serializationContextType, data, "NoName.txt", typedServices)!; + + _serializeAbstractMethod.Invoke(null, [sc, instance, _jsonFormatEnumValue]); return Encoding.UTF8.GetString(data.GetBuffer().AsSpan()[..(int)data.Length]); } @@ -160,18 +166,25 @@ public string SerializeObject(object instance, params object[] services) private InputActions LoadInputActions() { var actions = new InputActions(); - var inputActionDefinitionType = FindType("InputActionDefinition"); - var actionsDir = GameFacts.GetActionsPath(this.BaseGamePath); - Parallel.ForEach(Directory.EnumerateFiles(actionsDir), file => + + object syncObj = new object(); + + // 1. FILTER: Only process actual data files to stop blind SerializationExceptions + var validFiles = Directory.EnumerateFiles(actionsDir).Where(f => f.EndsWith(".def", StringComparison.OrdinalIgnoreCase) || f.EndsWith(".json", StringComparison.OrdinalIgnoreCase)); + + Parallel.ForEach(validFiles, file => { try { var def = DeserializeFile(file); + if (def == null) return; + // We know it has a Guid now, so the dynamic binder won't crash Guid id = def.Guid; - lock (actionsDir) + + lock (syncObj) { actions.Actions.Add(id, new InputActions.InputActionInfo { @@ -183,10 +196,11 @@ private InputActions LoadInputActions() } } catch - { } + { + // Ignore any files that genuinely fail serialization + } }); - var proxy = new ProxyGenerator(); var mapGuidToDefinitionInstance = proxy.CreateClassProxy ( @@ -225,11 +239,24 @@ private InputIds LoadInputIds() if (kind is null) continue; - var inputId = DynamicHelper.Unwrap(input.GetValue(null).AsDynamic().Id); - kind.Add(inputId); - - dynamicProvider.TryGetName(inputId, out string displayName); - inputIds.InputIdToDisplayName.Add(inputId, displayName); + try + { + var val = input.GetValue(null); + if (val == null) continue; + + // 3. SAFE REFLECTION: Check if the 'Id' property exists before touching the dynamic binder + var type = val.GetType(); + if (type.GetProperty("Id") == null && type.GetField("Id") == null) + continue; // Safely skip without throwing a RuntimeBinderException + + var inputId = DynamicHelper.Unwrap(val.AsDynamic().Id); + kind.Add(inputId); + + dynamicProvider.TryGetName(inputId, out string displayName); + inputIds.InputIdToDisplayName.Add(inputId, displayName); + } + catch + { } } } @@ -257,11 +284,11 @@ public void Intercept(IInvocation invocation) if (methodName == "TryLocateDefinition" && invocation.Arguments.Length == 3) { - var id = (Guid) invocation.Arguments[0]!; - var type = (Type) invocation.Arguments[1]!; + var id = (Guid)invocation.Arguments[0]!; + var type = (Type)invocation.Arguments[1]!; invocation.Arguments[2] = this.Actions.TryGetInputActionInfo(id)?.DefinitionInstanceStub ?? DefinitionRocks.AllocateDefinitionStub(type, id); - + invocation.ReturnValue = true; } else diff --git a/Data/PresetVM.cs b/Data/PresetVM.cs index 313b6c3..90fe54a 100644 --- a/Data/PresetVM.cs +++ b/Data/PresetVM.cs @@ -1,10 +1,6 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using ReflectionMagic; +using ReflectionMagic; using SpaceEditor.Rocks; +using System.Collections; namespace SpaceEditor.Data; @@ -24,7 +20,7 @@ public string Key public string DataString { get; private set; } public object RootObject { get; private set; } public IDictionary Bindings { get; private set; } - + public PresetVM(GameProxy game) { this.Game = game; @@ -35,11 +31,11 @@ public PresetVM(GameProxy game) var def = this.Actions.TryGetInputActionInfo(id)?.DefinitionInstanceStub; if (def is null) return null; - + if (this.Bindings.Contains(def) == false) return null; - - return (IList) this.Bindings[def]!; + + return (IList)this.Bindings[def]!; } public void LoadDataString(string content) @@ -49,9 +45,9 @@ public void LoadDataString(string content) this.DataString = content; this.RootObject = DynamicHelper.Unwrap(mappings); - this.Bindings = (IDictionary) DynamicHelper.Unwrap(mappings.ControlsPerAction); + this.Bindings = (IDictionary)DynamicHelper.Unwrap(mappings.ControlsPerAction); } - + public string ToDataString() { return this.Game.SerializeObject(this.RootObject); diff --git a/Data/Settings.cs b/Data/Settings.cs index d75b5e7..ce835da 100644 --- a/Data/Settings.cs +++ b/Data/Settings.cs @@ -1,6 +1,6 @@ -using System.Text; +using SpaceEditor.Rocks; +using System.Text; using System.Windows; -using SpaceEditor.Rocks; namespace SpaceEditor.Data; @@ -21,12 +21,12 @@ public Settings() // this.SettingsSaving += this.SettingsSavingEventHandler; // } - + private void SettingChangingEventHandler(object sender, System.Configuration.SettingChangingEventArgs e) { // Add code to handle the SettingChangingEvent event here. } - + private void SettingsSavingEventHandler(object sender, System.ComponentModel.CancelEventArgs e) { // Add code to handle the SettingsSaving event here. diff --git a/Data/VM.cs b/Data/VM.cs index 94bf2ae..db858d0 100644 --- a/Data/VM.cs +++ b/Data/VM.cs @@ -1,19 +1,19 @@ -using System.ComponentModel; +using SpaceEditor.Rocks; +using System.ComponentModel; using System.Runtime.CompilerServices; -using SpaceEditor.Rocks; namespace SpaceEditor.Data; public abstract class VM : INotifyPropertyChanged { public event PropertyChangedEventHandler? PropertyChanged; - + public IDisposable Bind(string property, Action consumer) { var source = GetType().GetProperty(property)!.GetMethod; consumer(Getvalue()); - + PropertyChangedEventHandler handler = (sender, args) => { consumer(Getvalue()); @@ -23,13 +23,13 @@ public IDisposable Bind(string property, Action consumer) { this.PropertyChanged -= handler; }); - + T Getvalue() { - return (T) source.Invoke(this, null); + return (T)source.Invoke(this, null); } } - + protected bool SetField(ref T field, T value, [CallerMemberName] string? propertyName = null) { if (EqualityComparer.Default.Equals(field, value)) diff --git a/MainWindow.xaml b/MainWindow.xaml index bfdb96e..a8d99b2 100644 --- a/MainWindow.xaml +++ b/MainWindow.xaml @@ -10,7 +10,7 @@ Title="Space Editor" Height="550" Width="1000" - d:DataContext="{local:MainWindow}" + d:DataContext="{d:DesignInstance Type=local:MainWindow, IsDesignTimeCreatable=True}" > Settings.Default; private GameLink? GameLink; + // Token to ensure overlapping reloads are safely aborted + private CancellationTokenSource? _reloadCts; + public string GamePath { get => this.Settings.GamePath; @@ -44,7 +45,7 @@ public MainWindow() try { var latest = await VersionChecker.GetLatestVersionInfo(); - if (latest.Published.Date != BuildInfo.BuildTimeUtc.Date) + if (latest.Published.Date > BuildInfo.BuildTimeUtc.Date) { this.UpdateHints.Visibility = Visibility.Visible; } @@ -56,6 +57,11 @@ public MainWindow() private async void Reload(object sender, RoutedEventArgs e) { + // Abort any currently running background initialization + _reloadCts?.Cancel(); + _reloadCts = new CancellationTokenSource(); + var token = _reloadCts.Token; + var tabs = this.MainTabs.Items; while (tabs.Count > 1) { @@ -68,17 +74,36 @@ private async void Reload(object sender, RoutedEventArgs e) return; } - this.InfoText.Text = "Loading ..."; + this.InfoText.Text = "Loading Engine and Game Data in background... (This may take a moment)"; + try { + // Offload directory searching and math to the background thread var game = await Task.Run(async () => { - var game = new GameProxy(this.GamePath); - _ = await game.InputActions; - _ = await game.InputIds; - return game; - }); + // 1. Initialize GameProxy (Searches directory, loads assemblies) + var proxy = new GameProxy(this.GamePath); + _ = await proxy.InputActions; + _ = await proxy.InputIds; + + token.ThrowIfCancellationRequested(); + + // 2. Pre-warm the Blueprint Generator's heavy static meshes in the background. + _ = Algorithms.GridShaper.ShapeDB.LargeShapes; + _ = Algorithms.GridShaper.ShapeDB.MidShapes; + + // 3. Pre-compile the ILGPU Kernel in the background + _ = Algorithms.GridShaper.GpuSetup.Accelerator; + + return proxy; + }, token); + // Double check cancellation before touching the main thread engine or UI + if (token.IsCancellationRequested) return; + + // 3. Initialize the VRage Engine connection ON THE MAIN THREAD. + // Game engines crash (Exit Code 1) if initialized on a background thread. + var newGameLink = new GameLink(game); var propertyGridFactoryKey = "CompositePropertyGridControlFactory"; this.Resources.Remove(propertyGridFactoryKey); this.Resources.Add(propertyGridFactoryKey, new CompositePropertyGridControlFactory @@ -110,7 +135,7 @@ private async void Reload(object sender, RoutedEventArgs e) await this.GameLink.DisposeAsync(); } - this.GameLink = new GameLink(game); + this.GameLink = newGameLink; tabs.Add(new TabItem { @@ -127,7 +152,7 @@ private async void Reload(object sender, RoutedEventArgs e) var sb = new StringBuilder(); sb.AppendLine("Loading finished"); sb.AppendLine(); - + sb.AppendLine("Main Assembly:"); var gameExe = game.MainAssembly; sb.AppendLine($"{gameExe.GetName().Name}"); @@ -136,9 +161,13 @@ private async void Reload(object sender, RoutedEventArgs e) sb.AppendLine(); sb.AppendLine("Use Tabs above to access individual features"); - + this.InfoText.Text = sb.ToString(); } + catch (OperationCanceledException) + { + // Silently handle task cancellation if the user changes the path rapidly + } catch (Exception ex) { this.InfoText.Text = "Exception happened during initial loading:" + Environment.NewLine + ex; diff --git a/Rocks/AsyncLazy.cs b/Rocks/AsyncLazy.cs index 2a5f327..54a5876 100644 --- a/Rocks/AsyncLazy.cs +++ b/Rocks/AsyncLazy.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Text; -using System.Threading.Tasks; +using System.Runtime.CompilerServices; namespace SpaceEditor.Rocks; diff --git a/Rocks/Base6Directions.cs b/Rocks/Base6Directions.cs index 356c044..db5ece6 100644 --- a/Rocks/Base6Directions.cs +++ b/Rocks/Base6Directions.cs @@ -1,7 +1,4 @@ using g4; -using System; -using System.Collections.Generic; -using System.Linq; namespace SpaceEditor.Rocks; diff --git a/Rocks/CollectionRocks.cs b/Rocks/CollectionRocks.cs index acc056a..9f5f118 100644 --- a/Rocks/CollectionRocks.cs +++ b/Rocks/CollectionRocks.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.InteropServices; +using System.Runtime.InteropServices; namespace SpaceEditor.Rocks; @@ -11,12 +8,12 @@ public static TValue GetOrAdd ( this Dictionary dictionary, TKey key, - Funcfactory + Func factory ) where TKey : notnull { ref var value = ref CollectionsMarshal.GetValueRefOrAddDefault(dictionary, key, out var existed); - + if (existed == false) { value = factory(key); diff --git a/Rocks/DefinitionRocks.cs b/Rocks/DefinitionRocks.cs index e9ff2a1..aa0ea3b 100644 --- a/Rocks/DefinitionRocks.cs +++ b/Rocks/DefinitionRocks.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using ReflectionMagic; +using ReflectionMagic; namespace SpaceEditor.Rocks; diff --git a/Rocks/Disposable.cs b/Rocks/Disposable.cs index a215381..ef3137c 100644 --- a/Rocks/Disposable.cs +++ b/Rocks/Disposable.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace SpaceEditor.Rocks; +namespace SpaceEditor.Rocks; public class Disposable : IDisposable { diff --git a/Rocks/EnumerableRocks.cs b/Rocks/EnumerableRocks.cs index 58bcd7e..301533e 100644 --- a/Rocks/EnumerableRocks.cs +++ b/Rocks/EnumerableRocks.cs @@ -30,7 +30,7 @@ public static class EnumerableRocks first = false; current = enumerator.Current; } - + yield return (current, first, Last: true); } diff --git a/Rocks/Enumerators.cs b/Rocks/Enumerators.cs index a5d4918..47cbb61 100644 --- a/Rocks/Enumerators.cs +++ b/Rocks/Enumerators.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using g4; +using g4; namespace SpaceEditor.Rocks; @@ -24,10 +19,10 @@ public static IEnumerable BoxRange(AxisAlignedBox3d box, ShiftGridInde public static IEnumerable BoxRange(Vector3i minInclusive, Vector3i maxInclusive) { for (int z = minInclusive.z; z <= maxInclusive.z; ++z) - for (int y = minInclusive.y; y <= maxInclusive.y; ++y) - for (int x = minInclusive.x; x <= maxInclusive.x; ++x) - { - yield return new(x, y, z); - } + for (int y = minInclusive.y; y <= maxInclusive.y; ++y) + for (int x = minInclusive.x; x <= maxInclusive.x; ++x) + { + yield return new(x, y, z); + } } } \ No newline at end of file diff --git a/Rocks/MathRocks.cs b/Rocks/MathRocks.cs index f55efd3..9f9e2cd 100644 --- a/Rocks/MathRocks.cs +++ b/Rocks/MathRocks.cs @@ -1,8 +1,4 @@ using g4; -using System; -using System.Buffers.Text; -using System.Collections.Generic; -using System.Linq; namespace SpaceEditor.Rocks; @@ -41,8 +37,8 @@ public static Frame3f ForwardUpTranslate(int forward, int up, Vector3f translate { return ForwardUpTranslate ( - (Vector3f) Base6Directions.Vectors[forward], - (Vector3f) Base6Directions.Vectors[up], + (Vector3f)Base6Directions.Vectors[forward], + (Vector3f)Base6Directions.Vectors[up], translate ); } @@ -118,7 +114,7 @@ public static IntersectResult IntersectWithTriangle(this AxisAlignedBox3d box, T { return BoxTriangleIntersection.IntersectBoxWithTriangle(box, tri); } - + public static Bitmap3 Clone(this Bitmap3 value) { var copy = new Bitmap3(value.Dimensions); diff --git a/Rocks/MeshRocks.cs b/Rocks/MeshRocks.cs index 1149074..acb79b2 100644 --- a/Rocks/MeshRocks.cs +++ b/Rocks/MeshRocks.cs @@ -1,7 +1,4 @@ using g4; -using System; -using System.Collections.Generic; -using System.Linq; namespace SpaceEditor.Rocks; @@ -9,7 +6,7 @@ public static class MeshRocks { public static IEnumerable EnumerateTriangles(this DMesh3 mesh) { - foreach(var triangle in mesh.Triangles()) + foreach (var triangle in mesh.Triangles()) { yield return new ( @@ -41,7 +38,7 @@ public static void AppendMesh(this DMesh3 target, DMesh3 source, Frame3f transfo var editor = new MeshEditor(target); editor.AppendMesh(source, out var newVertices); - foreach(var vertexId in newVertices) + foreach (var vertexId in newVertices) { var position = target.GetVertex(vertexId); //position = transform.Multiply(ref position); @@ -126,7 +123,7 @@ public static void AppendSlope(this DMesh3 mesh, AxisAlignedBox3d box, Vector3i var vertex = rotation.Multiply(ref vertices[vertexIndex]) * scale + origin; Vector3d normalD = normal; - normal = (Vector3f) rotation.Multiply(ref normalD); + normal = (Vector3f)rotation.Multiply(ref normalD); indices[i] = mesh.AppendVertex(new NewVertexInfo(vertex, normal)); } diff --git a/Rocks/NullToTemplateSelector.cs b/Rocks/NullToTemplateSelector.cs index 0bd84d8..855aebd 100644 --- a/Rocks/NullToTemplateSelector.cs +++ b/Rocks/NullToTemplateSelector.cs @@ -1,8 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using System.Windows; using System.Windows.Controls; -using System.Windows; namespace SpaceEditor.Rocks; diff --git a/Rocks/ReflectionRocks.cs b/Rocks/ReflectionRocks.cs index 817398d..bb71967 100644 --- a/Rocks/ReflectionRocks.cs +++ b/Rocks/ReflectionRocks.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections; -using System.Collections.Generic; +using System.Collections; using System.IO; using System.Reflection; @@ -10,9 +8,9 @@ public static class ReflectionRocks { public static Type? TryFindType(string assemblyHintPath, ReadOnlySpan probeAssemblies, string typeName) { - foreach(var assembly in probeAssemblies) + foreach (var assembly in probeAssemblies) { - if (GetLib(assemblyHintPath, assembly).TryFindType(typeName) is {} foundType) + if (GetLib(assemblyHintPath, assembly).TryFindType(typeName) is { } foundType) return foundType; } @@ -59,7 +57,7 @@ public static IEnumerable TryFindDerives(this Assembly assembly, Type base public static Assembly GetLib(string hintPath, string assembly) { var app = AppDomain.CurrentDomain; - if (TryGetPreloadedAssembly(assembly) is {} preloaded) + if (TryGetPreloadedAssembly(assembly) is { } preloaded) { return preloaded; } @@ -67,7 +65,7 @@ public static Assembly GetLib(string hintPath, string assembly) app.AssemblyResolve += (_, args) => { var requestedName = new AssemblyName(args.Name).Name; - if (TryGetPreloadedAssembly(requestedName) is {} preloaded) + if (TryGetPreloadedAssembly(requestedName) is { } preloaded) { return preloaded; } @@ -112,7 +110,7 @@ public static IEnumerable GetInstanceMembers(this Type declaringType public static object AllocateObjectBuilder(this Type obType) { var instance = Activator.CreateInstance(obType)!; - + foreach (var field in obType.GetInstanceMembers().OfType()) { if (typeof(ICollection).IsAssignableFrom(field.FieldType) == false) diff --git a/Rocks/StringCollectionRocks.cs b/Rocks/StringCollectionRocks.cs index b45e7b3..7511e0c 100644 --- a/Rocks/StringCollectionRocks.cs +++ b/Rocks/StringCollectionRocks.cs @@ -1,10 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Collections.Specialized; +using System.Collections.Specialized; using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace SpaceEditor.Rocks; diff --git a/Rocks/StringRocks.cs b/Rocks/StringRocks.cs index 71d9830..792b10b 100644 --- a/Rocks/StringRocks.cs +++ b/Rocks/StringRocks.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; +using System.IO; using System.Text; namespace SpaceEditor.Rocks; diff --git a/Rocks/ViewRocks.cs b/Rocks/ViewRocks.cs index ddae446..ee04dc0 100644 --- a/Rocks/ViewRocks.cs +++ b/Rocks/ViewRocks.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows; +using System.Windows; using System.Windows.Media; namespace SpaceEditor.Rocks; @@ -18,7 +13,7 @@ public static class ViewRocks if (current is T found) return found; - current = (FrameworkElement?) VisualTreeHelper.GetParent(current); + current = (FrameworkElement?)VisualTreeHelper.GetParent(current); } diff --git a/Services/VersionChecker.cs b/Services/VersionChecker.cs index e9f1349..aad7e36 100644 --- a/Services/VersionChecker.cs +++ b/Services/VersionChecker.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; +using System.Net.Http; using System.Text.Json; namespace SpaceEditor.Services; @@ -21,7 +18,7 @@ public static async Task GetLatestVersionInfo() var queryURL = $"https://api.github.com/repos/InflexCZE/SpaceEditor/releases/latest"; var latestReleaseJSON = await DownloadStringAsync(queryURL).ConfigureAwait(false); - + var data = JsonDocument.Parse(latestReleaseJSON).RootElement; var assets = data.GetProperty("assets").EnumerateArray(); return new() diff --git a/SpaceEditor.csproj b/SpaceEditor.csproj index ced0c52..b6237e9 100644 --- a/SpaceEditor.csproj +++ b/SpaceEditor.csproj @@ -22,6 +22,7 @@ + @@ -50,16 +51,7 @@ $(IntermediateOutputPath)BuildInfo.g.cs - DateTime.Parse("$(BuildDateTime)") %3B - } - " - Overwrite="true" /> + @@ -71,18 +63,11 @@ - + - + - +