This commit is contained in:
2026-04-04 13:04:05 -04:00
parent c35b663a1d
commit ad555c9341
1266 changed files with 59797 additions and 98 deletions

View File

@@ -1,88 +0,0 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2012
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SjkScripts", "SjkScripts", "{A04208C6-F985-C0CB-74E4-F24675E8FC46}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SjkScripts", "src\SjkScripts\SjkScripts\SjkScripts.csproj", "{981E9EB4-1B52-43F3-969D-76FCB8F21E6E}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "voxelgame", "voxelgame", "{CF22DAD7-7E92-97D6-EC07-2DE8EB5A2C9D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VoxelGame", "src\voxelgame\VoxelGame.csproj", "{01D4C862-C9DA-4444-A108-1D7C82B36B3C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
ExportDebug|Any CPU = ExportDebug|Any CPU
ExportDebug|x64 = ExportDebug|x64
ExportDebug|x86 = ExportDebug|x86
ExportRelease|Any CPU = ExportRelease|Any CPU
ExportRelease|x64 = ExportRelease|x64
ExportRelease|x86 = ExportRelease|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{981E9EB4-1B52-43F3-969D-76FCB8F21E6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{981E9EB4-1B52-43F3-969D-76FCB8F21E6E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{981E9EB4-1B52-43F3-969D-76FCB8F21E6E}.Debug|x64.ActiveCfg = Debug|Any CPU
{981E9EB4-1B52-43F3-969D-76FCB8F21E6E}.Debug|x64.Build.0 = Debug|Any CPU
{981E9EB4-1B52-43F3-969D-76FCB8F21E6E}.Debug|x86.ActiveCfg = Debug|Any CPU
{981E9EB4-1B52-43F3-969D-76FCB8F21E6E}.Debug|x86.Build.0 = Debug|Any CPU
{981E9EB4-1B52-43F3-969D-76FCB8F21E6E}.ExportDebug|Any CPU.ActiveCfg = Debug|Any CPU
{981E9EB4-1B52-43F3-969D-76FCB8F21E6E}.ExportDebug|Any CPU.Build.0 = Debug|Any CPU
{981E9EB4-1B52-43F3-969D-76FCB8F21E6E}.ExportDebug|x64.ActiveCfg = Debug|Any CPU
{981E9EB4-1B52-43F3-969D-76FCB8F21E6E}.ExportDebug|x64.Build.0 = Debug|Any CPU
{981E9EB4-1B52-43F3-969D-76FCB8F21E6E}.ExportDebug|x86.ActiveCfg = Debug|Any CPU
{981E9EB4-1B52-43F3-969D-76FCB8F21E6E}.ExportDebug|x86.Build.0 = Debug|Any CPU
{981E9EB4-1B52-43F3-969D-76FCB8F21E6E}.ExportRelease|Any CPU.ActiveCfg = Debug|Any CPU
{981E9EB4-1B52-43F3-969D-76FCB8F21E6E}.ExportRelease|Any CPU.Build.0 = Debug|Any CPU
{981E9EB4-1B52-43F3-969D-76FCB8F21E6E}.ExportRelease|x64.ActiveCfg = Debug|Any CPU
{981E9EB4-1B52-43F3-969D-76FCB8F21E6E}.ExportRelease|x64.Build.0 = Debug|Any CPU
{981E9EB4-1B52-43F3-969D-76FCB8F21E6E}.ExportRelease|x86.ActiveCfg = Debug|Any CPU
{981E9EB4-1B52-43F3-969D-76FCB8F21E6E}.ExportRelease|x86.Build.0 = Debug|Any CPU
{981E9EB4-1B52-43F3-969D-76FCB8F21E6E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{981E9EB4-1B52-43F3-969D-76FCB8F21E6E}.Release|Any CPU.Build.0 = Release|Any CPU
{981E9EB4-1B52-43F3-969D-76FCB8F21E6E}.Release|x64.ActiveCfg = Release|Any CPU
{981E9EB4-1B52-43F3-969D-76FCB8F21E6E}.Release|x64.Build.0 = Release|Any CPU
{981E9EB4-1B52-43F3-969D-76FCB8F21E6E}.Release|x86.ActiveCfg = Release|Any CPU
{981E9EB4-1B52-43F3-969D-76FCB8F21E6E}.Release|x86.Build.0 = Release|Any CPU
{01D4C862-C9DA-4444-A108-1D7C82B36B3C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{01D4C862-C9DA-4444-A108-1D7C82B36B3C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{01D4C862-C9DA-4444-A108-1D7C82B36B3C}.Debug|x64.ActiveCfg = Debug|Any CPU
{01D4C862-C9DA-4444-A108-1D7C82B36B3C}.Debug|x64.Build.0 = Debug|Any CPU
{01D4C862-C9DA-4444-A108-1D7C82B36B3C}.Debug|x86.ActiveCfg = Debug|Any CPU
{01D4C862-C9DA-4444-A108-1D7C82B36B3C}.Debug|x86.Build.0 = Debug|Any CPU
{01D4C862-C9DA-4444-A108-1D7C82B36B3C}.ExportDebug|Any CPU.ActiveCfg = ExportDebug|Any CPU
{01D4C862-C9DA-4444-A108-1D7C82B36B3C}.ExportDebug|Any CPU.Build.0 = ExportDebug|Any CPU
{01D4C862-C9DA-4444-A108-1D7C82B36B3C}.ExportDebug|x64.ActiveCfg = ExportDebug|Any CPU
{01D4C862-C9DA-4444-A108-1D7C82B36B3C}.ExportDebug|x64.Build.0 = ExportDebug|Any CPU
{01D4C862-C9DA-4444-A108-1D7C82B36B3C}.ExportDebug|x86.ActiveCfg = ExportDebug|Any CPU
{01D4C862-C9DA-4444-A108-1D7C82B36B3C}.ExportDebug|x86.Build.0 = ExportDebug|Any CPU
{01D4C862-C9DA-4444-A108-1D7C82B36B3C}.ExportRelease|Any CPU.ActiveCfg = ExportRelease|Any CPU
{01D4C862-C9DA-4444-A108-1D7C82B36B3C}.ExportRelease|Any CPU.Build.0 = ExportRelease|Any CPU
{01D4C862-C9DA-4444-A108-1D7C82B36B3C}.ExportRelease|x64.ActiveCfg = ExportRelease|Any CPU
{01D4C862-C9DA-4444-A108-1D7C82B36B3C}.ExportRelease|x64.Build.0 = ExportRelease|Any CPU
{01D4C862-C9DA-4444-A108-1D7C82B36B3C}.ExportRelease|x86.ActiveCfg = ExportRelease|Any CPU
{01D4C862-C9DA-4444-A108-1D7C82B36B3C}.ExportRelease|x86.Build.0 = ExportRelease|Any CPU
{01D4C862-C9DA-4444-A108-1D7C82B36B3C}.Release|Any CPU.ActiveCfg = Debug|Any CPU
{01D4C862-C9DA-4444-A108-1D7C82B36B3C}.Release|Any CPU.Build.0 = Debug|Any CPU
{01D4C862-C9DA-4444-A108-1D7C82B36B3C}.Release|x64.ActiveCfg = Debug|Any CPU
{01D4C862-C9DA-4444-A108-1D7C82B36B3C}.Release|x64.Build.0 = Debug|Any CPU
{01D4C862-C9DA-4444-A108-1D7C82B36B3C}.Release|x86.ActiveCfg = Debug|Any CPU
{01D4C862-C9DA-4444-A108-1D7C82B36B3C}.Release|x86.Build.0 = Debug|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{A04208C6-F985-C0CB-74E4-F24675E8FC46} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
{981E9EB4-1B52-43F3-969D-76FCB8F21E6E} = {A04208C6-F985-C0CB-74E4-F24675E8FC46}
{CF22DAD7-7E92-97D6-EC07-2DE8EB5A2C9D} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
{01D4C862-C9DA-4444-A108-1D7C82B36B3C} = {CF22DAD7-7E92-97D6-EC07-2DE8EB5A2C9D}
EndGlobalSection
EndGlobal

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
uid://q2phyhqtiacu

View File

@@ -0,0 +1,45 @@
// public interface IVoxelGraph<TVoxel>
// {
// bool Is
// }
using System;
using System.Collections.Generic;
using Godot;
using SJK.Functional;
using SJK.Voxels;
public static class VoxelFloodFill
{
public static IEnumerable<(Vector3I,uint)> Fill(Vector3I start, Func<Vector3I, bool> predicate, uint maxCount = uint.MaxValue, uint maxValue = uint.MaxValue)
{
var visited = new HashSet<Vector3I>();
var queue = new Queue<(Vector3I Position, uint Distance)>();
queue.Enqueue((start, 0));
visited.Add(start);
while (queue.Count > 0)
{
(var pos, var distance) = queue.Dequeue();
yield return (pos, distance);
if (visited.Count >= maxCount)
yield break;
for (int i = 0; i < 6; i++)
{
var offset = ((Direction)i).ToOffset();
if (distance >= maxValue)
continue;
if (predicate(pos + offset))
{
continue;
}
if (!visited.Add(pos + offset))
{
continue;
}
queue.Enqueue((pos + offset, distance + 1));
}
}
}
}

View File

@@ -0,0 +1 @@
uid://bb46scw4c0yhh

View File

@@ -5,4 +5,8 @@ using System;
public partial class NewScript : Node public partial class NewScript : Node
{ {
IOption<int> OptionTest; IOption<int> OptionTest;
void test()
{
}
} }

View File

@@ -0,0 +1,194 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using SqlKata;
public sealed class SimpleDataBase : IDisposable
{
//TODO Need an value to store table index, and offset to index as when laoding, database does not know where database entrys start
private const int MAGIC = 0x53444231; // "SDB1"
private const int VERSION = 0;
private const int INITIAILBLOCKS = 32;
private const int BLOCKSSTART = sizeof(int) *3 + sizeof(long);
private const int BLOCKDATASIZE = sizeof(long) + sizeof(int) + sizeof(bool);
private readonly Stream stream;
bool _dirty = true;
private List<BlockData> blocks = new(32);
public SimpleDataBase(string filePath) : this(new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None)) { }
public SimpleDataBase(Stream stream)
{
this.stream = stream;
Load();
}
public int Allocate(byte[] data)
{
for (int i = 0; i < blocks.Count; i++)
{
var block = blocks[i];
if (!block.InUse && block.Size <= data.Length)
{
WriteBlock(block.Offset, data);
block.Size = data.Length;
block.InUse = true;
blocks[i] = block;
_dirty = true;
return i;
}
}
stream.Seek(0, SeekOrigin.End);
BlockData newBlock = new();
newBlock.Offset = stream.Position;
stream.Write(data);
newBlock.Size = data.Length;
newBlock.InUse = true;
_dirty = true;
blocks.Add(newBlock);
Godot.GD.PrintS(newBlock.Offset, newBlock.Size, newBlock.InUse);
return blocks.Count - 1;
}
public IEnumerable<int> GetTableEnrtys()
{
for (int i = 0; i < blocks.Count; i++)
{
if (blocks[i].InUse)
yield return i;
}
}
public byte[] Get(int id)
{
if (id < 0 || id >= blocks.Count)
throw new ArgumentOutOfRangeException(nameof(id));
var blk = blocks[id];
if (!blk.InUse)
throw new InvalidOperationException("Block deleted.");
var block = blocks[id];
stream.Seek(block.Offset, SeekOrigin.Begin);
byte[] bytes = new byte[block.Size];
var error = stream.Read(bytes, 0, block.Size);
return bytes;
}
private void Load()
{
if (stream.Length < sizeof(int))
{
CreateHeader();
}
using var reader = new BinaryReader(stream, encoding: Encoding.UTF8, leaveOpen: true);
stream.Seek(0, SeekOrigin.Begin);
if (reader.ReadInt32() != MAGIC)
{
stream.Close();
throw new Exception("Stream does not have Magic Number, CLosing Stream");
}
if (reader.ReadInt32() != VERSION)
{
stream.Close();
throw new Exception($"Stream Version does not match");
}
var blockData = GetRootTable();
if (blockData.Offset >= 0 && blockData.Size >=0)
{
stream.Seek(blockData.Offset, SeekOrigin.Begin);
blocks = new(BytesToTable(stream));
}
}
public void Save()
{
// if (!_dirty)
// {
// return;
// }
// var blockData = GetRootTable();
// if (table >= 0)
// {
// Delete(table);
// }
var id = Allocate(TableToBytes(blocks.ToArray()));
var blockData = blocks[id];
SetRootTable(blockData);
_dirty = false;
}
private byte[] TableToBytes(BlockData[] blocks)
{
using var memory = new MemoryStream();
using var writer = new BinaryWriter(memory);
writer.Write(blocks.Length);
for (int i = 0; i < blocks.Length; i++)
{
WriteBlockData(writer, blocks[i]);
Godot.GD.PrintS(blocks[i].Size, blocks[i].Offset, blocks[i].InUse);
}
return memory.ToArray();
}
private BlockData[] BytesToTable(Stream stream)
{
using var reader = new BinaryReader(stream, Encoding.UTF8,leaveOpen: true);
var length = reader.ReadInt32();
BlockData[] blockDatas = new BlockData[length];
for (int i = 0; i < length; i++)
{
blockDatas[i] = ReadBlockData(reader);
Godot.GD.PrintS(blockDatas[i].Size, blockDatas[i].Offset, blockDatas[i].InUse);
}
return blockDatas;
}
public void Delete(int id)
{
var block = blocks[id];
block.InUse = false;
blocks[id] = block;
Godot.GD.Print("ID ", id);
}
private BlockData ReadBlockData(BinaryReader reader) => new BlockData() { Offset = reader.ReadInt64(), Size = reader.ReadInt32(), InUse = reader.ReadBoolean() };
private void WriteBlockData(BinaryWriter writer, BlockData block) {
writer.Write(block.Offset);
writer.Write(block.Size);
writer.Write(block.InUse);
}
private void CreateHeader()
{
using var writer = new BinaryWriter(stream,Encoding.UTF8,leaveOpen: true);
writer.Write(MAGIC);
writer.Write(VERSION);
WriteBlockData(writer,new BlockData(){Offset = -1,Size = -1, InUse = false});
}
private void SetRootTable(BlockData block) {
using var writer = new BinaryWriter(stream, Encoding.UTF8, leaveOpen: true);
stream.Seek(sizeof(int) * 2, SeekOrigin.Begin);
WriteBlockData(writer, block);
}
private BlockData GetRootTable() {
stream.Seek(sizeof(int) * 2, SeekOrigin.Begin);
using var reader = new BinaryReader(stream, Encoding.UTF8, leaveOpen: true);
return ReadBlockData(reader);
}
private void WriteBlock(long offset, byte[] data)
{
stream.Seek(offset, SeekOrigin.Begin);
stream.Write(data);
}
[StructLayout(LayoutKind.Sequential)]
private struct BlockData
{
public long Offset;
public int Size;
public bool InUse;
}
public void Dispose()
{
Save();
stream?.Dispose();
}
}

View File

@@ -0,0 +1 @@
uid://duss0kqmyo2ee

View File

@@ -1,9 +1,19 @@
<Project Sdk="Godot.NET.Sdk/4.4.1"> <Project Sdk="Godot.NET.Sdk/4.5.1">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<EnableDynamicLoading>true</EnableDynamicLoading> <EnableDynamicLoading>true</EnableDynamicLoading>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<!-- <PackageReference Include="Dapper" Version="2.1.66" /> -->
<PackageReference Include="Microsoft.Data.Sqlite" Version="8.0.0" />
<PackageReference Include="protobuf-net" Version="3.2.56" />
<PackageReference Include="SqlKata" Version="4.0.1" />
<PackageReference Include="SqlKata.Execution" Version="4.0.1" />
<ProjectReference Include="..\SjkScripts\SjkScripts\SjkScripts.csproj" /> <ProjectReference Include="..\SjkScripts\SjkScripts\SjkScripts.csproj" />
<ProjectReference Include="..\SjkScripts\SJKGodotHelpers\SJKGodotHelpers.csproj" />
<PackageReference Include="ImGui.NET" Version="1.91.6.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -0,0 +1,19 @@
<Project Sdk="Godot.NET.Sdk/4.4.1">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<EnableDynamicLoading>true</EnableDynamicLoading>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<!-- <PackageReference Include="Dapper" Version="2.1.66" /> -->
<PackageReference Include="Microsoft.Data.Sqlite" Version="8.0.0" />
<PackageReference Include="protobuf-net" Version="3.2.56" />
<PackageReference Include="SqlKata" Version="4.0.1" />
<PackageReference Include="SqlKata.Execution" Version="4.0.1" />
<ProjectReference Include="..\SjkScripts\SjkScripts\SjkScripts.csproj" />
<ProjectReference Include="..\SjkScripts\SJKGodotHelpers\SJKGodotHelpers.csproj" />
<PackageReference Include="ImGui.NET" Version="1.91.6.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
</Project>

102
src/voxelgame/VoxelGame.sln Normal file
View File

@@ -0,0 +1,102 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2012
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VoxelGame", "VoxelGame.csproj", "{2D3A7228-05F3-44F1-B105-AED07769FB30}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SjkScripts", "..\SjkScripts\SjkScripts\SjkScripts.csproj", "{CB613391-62B4-486F-8E89-693B981EFACD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SJKGodotHelpers", "..\SjkScripts\SJKGodotHelpers\SJKGodotHelpers.csproj", "{FB3F8FCC-6245-4D99-8268-F4663885ADC5}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
ExportDebug|Any CPU = ExportDebug|Any CPU
ExportDebug|x64 = ExportDebug|x64
ExportDebug|x86 = ExportDebug|x86
ExportRelease|Any CPU = ExportRelease|Any CPU
ExportRelease|x64 = ExportRelease|x64
ExportRelease|x86 = ExportRelease|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{2D3A7228-05F3-44F1-B105-AED07769FB30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2D3A7228-05F3-44F1-B105-AED07769FB30}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2D3A7228-05F3-44F1-B105-AED07769FB30}.Debug|x64.ActiveCfg = Debug|Any CPU
{2D3A7228-05F3-44F1-B105-AED07769FB30}.Debug|x64.Build.0 = Debug|Any CPU
{2D3A7228-05F3-44F1-B105-AED07769FB30}.Debug|x86.ActiveCfg = Debug|Any CPU
{2D3A7228-05F3-44F1-B105-AED07769FB30}.Debug|x86.Build.0 = Debug|Any CPU
{2D3A7228-05F3-44F1-B105-AED07769FB30}.ExportDebug|Any CPU.ActiveCfg = ExportDebug|Any CPU
{2D3A7228-05F3-44F1-B105-AED07769FB30}.ExportDebug|Any CPU.Build.0 = ExportDebug|Any CPU
{2D3A7228-05F3-44F1-B105-AED07769FB30}.ExportDebug|x64.ActiveCfg = ExportDebug|Any CPU
{2D3A7228-05F3-44F1-B105-AED07769FB30}.ExportDebug|x64.Build.0 = ExportDebug|Any CPU
{2D3A7228-05F3-44F1-B105-AED07769FB30}.ExportDebug|x86.ActiveCfg = ExportDebug|Any CPU
{2D3A7228-05F3-44F1-B105-AED07769FB30}.ExportDebug|x86.Build.0 = ExportDebug|Any CPU
{2D3A7228-05F3-44F1-B105-AED07769FB30}.ExportRelease|Any CPU.ActiveCfg = ExportRelease|Any CPU
{2D3A7228-05F3-44F1-B105-AED07769FB30}.ExportRelease|Any CPU.Build.0 = ExportRelease|Any CPU
{2D3A7228-05F3-44F1-B105-AED07769FB30}.ExportRelease|x64.ActiveCfg = ExportRelease|Any CPU
{2D3A7228-05F3-44F1-B105-AED07769FB30}.ExportRelease|x64.Build.0 = ExportRelease|Any CPU
{2D3A7228-05F3-44F1-B105-AED07769FB30}.ExportRelease|x86.ActiveCfg = ExportRelease|Any CPU
{2D3A7228-05F3-44F1-B105-AED07769FB30}.ExportRelease|x86.Build.0 = ExportRelease|Any CPU
{2D3A7228-05F3-44F1-B105-AED07769FB30}.Release|Any CPU.ActiveCfg = Debug|Any CPU
{2D3A7228-05F3-44F1-B105-AED07769FB30}.Release|Any CPU.Build.0 = Debug|Any CPU
{2D3A7228-05F3-44F1-B105-AED07769FB30}.Release|x64.ActiveCfg = Debug|Any CPU
{2D3A7228-05F3-44F1-B105-AED07769FB30}.Release|x64.Build.0 = Debug|Any CPU
{2D3A7228-05F3-44F1-B105-AED07769FB30}.Release|x86.ActiveCfg = Debug|Any CPU
{2D3A7228-05F3-44F1-B105-AED07769FB30}.Release|x86.Build.0 = Debug|Any CPU
{CB613391-62B4-486F-8E89-693B981EFACD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CB613391-62B4-486F-8E89-693B981EFACD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CB613391-62B4-486F-8E89-693B981EFACD}.Debug|x64.ActiveCfg = Debug|Any CPU
{CB613391-62B4-486F-8E89-693B981EFACD}.Debug|x64.Build.0 = Debug|Any CPU
{CB613391-62B4-486F-8E89-693B981EFACD}.Debug|x86.ActiveCfg = Debug|Any CPU
{CB613391-62B4-486F-8E89-693B981EFACD}.Debug|x86.Build.0 = Debug|Any CPU
{CB613391-62B4-486F-8E89-693B981EFACD}.ExportDebug|Any CPU.ActiveCfg = Debug|Any CPU
{CB613391-62B4-486F-8E89-693B981EFACD}.ExportDebug|Any CPU.Build.0 = Debug|Any CPU
{CB613391-62B4-486F-8E89-693B981EFACD}.ExportDebug|x64.ActiveCfg = Debug|Any CPU
{CB613391-62B4-486F-8E89-693B981EFACD}.ExportDebug|x64.Build.0 = Debug|Any CPU
{CB613391-62B4-486F-8E89-693B981EFACD}.ExportDebug|x86.ActiveCfg = Debug|Any CPU
{CB613391-62B4-486F-8E89-693B981EFACD}.ExportDebug|x86.Build.0 = Debug|Any CPU
{CB613391-62B4-486F-8E89-693B981EFACD}.ExportRelease|Any CPU.ActiveCfg = Debug|Any CPU
{CB613391-62B4-486F-8E89-693B981EFACD}.ExportRelease|Any CPU.Build.0 = Debug|Any CPU
{CB613391-62B4-486F-8E89-693B981EFACD}.ExportRelease|x64.ActiveCfg = Debug|Any CPU
{CB613391-62B4-486F-8E89-693B981EFACD}.ExportRelease|x64.Build.0 = Debug|Any CPU
{CB613391-62B4-486F-8E89-693B981EFACD}.ExportRelease|x86.ActiveCfg = Debug|Any CPU
{CB613391-62B4-486F-8E89-693B981EFACD}.ExportRelease|x86.Build.0 = Debug|Any CPU
{CB613391-62B4-486F-8E89-693B981EFACD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CB613391-62B4-486F-8E89-693B981EFACD}.Release|Any CPU.Build.0 = Release|Any CPU
{CB613391-62B4-486F-8E89-693B981EFACD}.Release|x64.ActiveCfg = Release|Any CPU
{CB613391-62B4-486F-8E89-693B981EFACD}.Release|x64.Build.0 = Release|Any CPU
{CB613391-62B4-486F-8E89-693B981EFACD}.Release|x86.ActiveCfg = Release|Any CPU
{CB613391-62B4-486F-8E89-693B981EFACD}.Release|x86.Build.0 = Release|Any CPU
{FB3F8FCC-6245-4D99-8268-F4663885ADC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FB3F8FCC-6245-4D99-8268-F4663885ADC5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FB3F8FCC-6245-4D99-8268-F4663885ADC5}.Debug|x64.ActiveCfg = Debug|Any CPU
{FB3F8FCC-6245-4D99-8268-F4663885ADC5}.Debug|x64.Build.0 = Debug|Any CPU
{FB3F8FCC-6245-4D99-8268-F4663885ADC5}.Debug|x86.ActiveCfg = Debug|Any CPU
{FB3F8FCC-6245-4D99-8268-F4663885ADC5}.Debug|x86.Build.0 = Debug|Any CPU
{FB3F8FCC-6245-4D99-8268-F4663885ADC5}.ExportDebug|Any CPU.ActiveCfg = Debug|Any CPU
{FB3F8FCC-6245-4D99-8268-F4663885ADC5}.ExportDebug|Any CPU.Build.0 = Debug|Any CPU
{FB3F8FCC-6245-4D99-8268-F4663885ADC5}.ExportDebug|x64.ActiveCfg = Debug|Any CPU
{FB3F8FCC-6245-4D99-8268-F4663885ADC5}.ExportDebug|x64.Build.0 = Debug|Any CPU
{FB3F8FCC-6245-4D99-8268-F4663885ADC5}.ExportDebug|x86.ActiveCfg = Debug|Any CPU
{FB3F8FCC-6245-4D99-8268-F4663885ADC5}.ExportDebug|x86.Build.0 = Debug|Any CPU
{FB3F8FCC-6245-4D99-8268-F4663885ADC5}.ExportRelease|Any CPU.ActiveCfg = Debug|Any CPU
{FB3F8FCC-6245-4D99-8268-F4663885ADC5}.ExportRelease|Any CPU.Build.0 = Debug|Any CPU
{FB3F8FCC-6245-4D99-8268-F4663885ADC5}.ExportRelease|x64.ActiveCfg = Debug|Any CPU
{FB3F8FCC-6245-4D99-8268-F4663885ADC5}.ExportRelease|x64.Build.0 = Debug|Any CPU
{FB3F8FCC-6245-4D99-8268-F4663885ADC5}.ExportRelease|x86.ActiveCfg = Debug|Any CPU
{FB3F8FCC-6245-4D99-8268-F4663885ADC5}.ExportRelease|x86.Build.0 = Debug|Any CPU
{FB3F8FCC-6245-4D99-8268-F4663885ADC5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FB3F8FCC-6245-4D99-8268-F4663885ADC5}.Release|Any CPU.Build.0 = Release|Any CPU
{FB3F8FCC-6245-4D99-8268-F4663885ADC5}.Release|x64.ActiveCfg = Release|Any CPU
{FB3F8FCC-6245-4D99-8268-F4663885ADC5}.Release|x64.Build.0 = Release|Any CPU
{FB3F8FCC-6245-4D99-8268-F4663885ADC5}.Release|x86.ActiveCfg = Release|Any CPU
{FB3F8FCC-6245-4D99-8268-F4663885ADC5}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,142 @@
using System.Collections.Generic;
using Godot;
namespace SJK.Voxels;
public class BoxelMesher<TVoxel>
{
private readonly IBoxelMesherApi<TVoxel> api;
public BoxelMesher(IBoxelMesherApi<TVoxel> api)
{
this.api = api;
}
public Mesh Mesh(IVoxelChunk<TVoxel> values)
{
var vertices = new List<Vector3>();
var normals = new List<Vector3>();
var triangles = new List<int>();
for (int x = values.Bounds.Min.X; x < values.Bounds.Max.X; x++)
{
for (int y = values.Bounds.Min.Y; y < values.Bounds.Max.Y; y++)
{
for (int z = values.Bounds.Min.Z; z < values.Bounds.Max.Z; z++)
{
var pos = new Vector3I(x, y, z);
var voxel = values.GetVoxel(pos);
if (!api.IsSolid(voxel))
continue;
for (int i = 0; i < 6; i++)
{
var neighborPos = pos + ((Direction)i).ToOffset();
// GD.Print(neighborPos);
if (!values.Bounds.IsInBounds(neighborPos) || !api.IsSolid(values.GetVoxel(neighborPos)))
{
AddFace((Direction)i, pos, vertices, triangles, normals);
}
}
}
}
}
if (vertices.Count == 0)
return new BoxMesh();
Godot.Collections.Array surfaceArray = [];
surfaceArray.Resize((int)Godot.Mesh.ArrayType.Max);
surfaceArray[(int)Godot.Mesh.ArrayType.Vertex] = vertices.ToArray();
// surfaceArray[(int)Mesh.ArrayType.TexUV] = uvs.ToArray();
surfaceArray[(int)Godot.Mesh.ArrayType.Normal] = normals.ToArray();
surfaceArray[(int)Godot.Mesh.ArrayType.Index] = triangles.ToArray();
var arrMesh = new ArrayMesh();
// No blendshapes, lods, or compression used.
arrMesh.AddSurfaceFromArrays(Godot.Mesh.PrimitiveType.Triangles, surfaceArray);
return arrMesh;
}
private static void AddFace(Direction dir, Vector3 pos, List<Vector3> vertices, List<int> triangles,List<Vector3> normals)
{
int index = vertices.Count;
// Normal of the face
Vector3 normal = dir.ToOffset();
// Pick two orthogonal vectors to span the quad
Vector3 tangent, bitangent;
if (normal.X != 0) // Face along X → use Y,Z as axes
{
tangent = new Vector3(0, 1, 0);
bitangent = new Vector3(0, 0, -1);
}
else if (normal.Y != 0) // Face along Y → use X,Z as axes
{
tangent = new Vector3(1, 0, 0);
bitangent = new Vector3(0, 0, -1);
}
else // Face along Z → use X,Y as axes
{
tangent = new Vector3(1, 0, 0);
bitangent = new Vector3(0, 1, 0);
}
// Scale to half-unit cube
tangent *= 0.5f;
bitangent *= 0.5f;
normal *= 0.5f;
// Quad center is offset in face direction
Vector3 center = pos + normal;
// Generate 4 vertices of the quad
vertices.Add(center - tangent - bitangent); // bottom-left
vertices.Add(center + tangent - bitangent); // bottom-right
vertices.Add(center - tangent + bitangent); // top-left
vertices.Add(center + tangent + bitangent); // top-right
normals.Add(dir.ToOffset());
normals.Add(dir.ToOffset());
normals.Add(dir.ToOffset());
normals.Add(dir.ToOffset());
// Add two triangles (winding order consistent with normal)
if (normal.X>0 ||normal.Y<0 ||normal.Z<0 )
{
triangles.Add(index + 0);
triangles.Add(index + 1);
triangles.Add(index + 2);
triangles.Add(index + 1);
triangles.Add(index + 3);
triangles.Add(index + 2);
}
else
{
triangles.Add(index + 0);
triangles.Add(index + 2);
triangles.Add(index + 1);
triangles.Add(index + 1);
triangles.Add(index + 2);
triangles.Add(index + 3);
}
}
}
public interface IBoxelMesherApi<TVoxel>
{
public void GetUvs(TVoxel id, Direction direction);
public bool IsSolid(TVoxel id);
}
public static class DirectionExtensions
{
public static Vector3I ToOffset(this Direction dir) => dir switch
{
Direction.Forward => new Vector3I(0, 0, -1),
Direction.BackWord => new Vector3I(0, 0, 1),
Direction.Right => new Vector3I(1, 0, 0),
Direction.Left => new Vector3I(-1, 0, 0),
Direction.Up => new Vector3I(0, 1, 0),
Direction.Down => new Vector3I(0, -1, 0),
_ => Vector3I.Zero
};
}

View File

@@ -0,0 +1 @@
uid://puaveig1esds

View File

@@ -0,0 +1,464 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using System.Runtime.CompilerServices;
using Godot;
using GodotSteam; // or Godot's Vector3I if youre in Godot
public interface IChunkPos//: IEquatable<IChunkPos>
{
public int X { get; }
public int Y { get; }
public int Z { get; }
long ToKey();
}
public record struct Dimension(int Id);
public interface IChunkPos<TSelf> :
IChunkPos,
IAdditionOperators<TSelf, TSelf, TSelf>,
ISubtractionOperators<TSelf, TSelf, TSelf>,
IEquatable<TSelf>
where TSelf : struct, IChunkPos<TSelf>
{
static abstract TSelf Create(int x, int y, int z);
static abstract TSelf FromKey(long key);
// static abstract int MaxWidth {get;}
// static abstract int MaxHeight {get;}
// static abstract int MaxDepth {get;}
// static abstract int MinWidth {get;}
// static abstract int MinHeight {get;}
// static abstract int MinDepth {get;}
}
public readonly struct ChunkIndex3D :IChunkPos<ChunkIndex3D>,
IEquatable<ChunkIndex3D>,
IAdditionOperators<ChunkIndex3D, ChunkIndex3D, ChunkIndex3D>,
ISubtractionOperators<ChunkIndex3D, ChunkIndex3D, ChunkIndex3D>
{
private readonly long _value;
public long Value => _value;
// Bit sizes
const int ChunkXBits = 21;
const int ChunkZBits = 21;
const int ChunkYBits = 9;
const int GuardBits = 1;
const int DimensionBits = 10;
const int ReservedBits = 2;
// Bit shifts
const int ChunkXShift = 0;
const int ChunkZShift = ChunkXShift + ChunkXBits;
const int ChunkYShift = ChunkZShift + ChunkZBits;
const int GuardShift = ChunkYShift + ChunkYBits;
const int DimensionShift = GuardShift + GuardBits;
const int ReservedShift = DimensionShift + DimensionBits;
// Masks
const long ChunkXMask = (1L << ChunkXBits) - 1;
const long ChunkZMask = (1L << ChunkZBits) - 1;
const long ChunkYMask = (1L << ChunkYBits) - 1;
const long DimensionMask = (1L << DimensionBits) - 1;
public ChunkIndex3D(long packed)
{
_value = packed;
}
public ChunkIndex3D(int x, int y, int z, int dimension = 0)
{
_value =
((long)x & ChunkXMask) << ChunkXShift |
((long)z & ChunkZMask) << ChunkZShift |
((long)y & ChunkYMask) << ChunkYShift |
((long)dimension & DimensionMask) << DimensionShift;
}
// --------------------------------
// Field Accessors
// --------------------------------
public int X => (int)((_value >> ChunkXShift) & ChunkXMask);
public int Z => (int)((_value >> ChunkZShift) & ChunkZMask);
public int Y => (int)((_value >> ChunkYShift) & ChunkYMask);
public int Dimension => (int)((_value >> DimensionShift) & DimensionMask);
// --------------------------------
// Offset Helpers
// --------------------------------
public ChunkIndex3D Offset(int dx, int dy, int dz)
{
return new ChunkIndex3D(
X + dx,
Y + dy,
Z + dz,
Dimension
);
}
// --------------------------------
// Operators
// --------------------------------
public static ChunkIndex3D operator +(ChunkIndex3D a, ChunkIndex3D b)
{
if (a.Dimension != b.Dimension)
throw new InvalidOperationException("Cannot add indices from different dimensions.");
return new ChunkIndex3D(
a.X + b.X,
a.Y + b.Y,
a.Z + b.Z,
a.Dimension
);
}
public static ChunkIndex3D operator -(ChunkIndex3D a, ChunkIndex3D b)
{
if (a.Dimension != b.Dimension)
throw new InvalidOperationException("Cannot subtract indices from different dimensions.");
return new ChunkIndex3D(
a.X - b.X,
a.Y - b.Y,
a.Z - b.Z,
a.Dimension
);
}
// --------------------------------
// Equality
// --------------------------------
public bool Equals(ChunkIndex3D other) => _value == other._value;
public override bool Equals(object? obj)
=> obj is ChunkIndex3D other && Equals(other);
public override int GetHashCode() => _value.GetHashCode();
public static bool operator ==(ChunkIndex3D a, ChunkIndex3D b)
=> a._value == b._value;
public static bool operator !=(ChunkIndex3D a, ChunkIndex3D b)
=> a._value != b._value;
public override string ToString()
=> $"ChunkIndex3D({X},{Y},{Z},Dim:{Dimension})";
public static ChunkIndex3D Create(int x, int y, int z)=> new(x,y,z);
public static ChunkIndex3D FromKey(long key)=>new(key);
public long ToKey()=>Value;
// }
public static ChunkIndex3D FromGlobal(int x, int y, int z)
{
return new ChunkIndex3D(
x >> Chunk32.BitsPerAxis,
y >> Chunk32.BitsPerAxis,
z >> Chunk32.BitsPerAxis
);
}
}
public readonly struct ChunkPos<TChunk> : IEquatable<ChunkPos<TChunk>>, IChunkPos<ChunkPos<TChunk>> where TChunk : IChunkSize<TChunk>
{
public readonly Vector3I Value;
private readonly long _data;
public int X => Value.X;
public int Y => Value.Y;
public int Z => Value.Z;
public ChunkPos(Vector3I value)
{
if (value.X < MinWidth || value.Y < MinHeight || value.Z < MinDepth || value.X > MaxWidth || value.Y > MaxHeight || value.Z > MaxDepth )
{
throw new ArgumentOutOfRangeException(nameof(value));
}
Value = value;
}
public ChunkPos(int x, int y, int z) : this(new(x,y,z))
{
}
// Create from global voxel position
public static ChunkPos<TChunk> FromGlobal(Vector3I globalPos) => FromGlobal(globalPos.X, globalPos.Y, globalPos.Z);
// public static ChunkPos<TChunk> FromGlobal(int x, int y, int z)
// {
// return new ChunkPos<TChunk>(new Vector3I(
// Math.DivRem(x, TChunk.Size, out int _),
// Math.DivRem(y, TChunk.Size, out int _),
// Math.DivRem(z, TChunk.Size, out int _)
// ));
// }
public static ChunkPos<TChunk> FromGlobal(int x, int y, int z)
{
return new ChunkPos<TChunk>(new Vector3I(
x >> TChunk.BitsPerAxis,
y >> TChunk.BitsPerAxis,
z >> TChunk.BitsPerAxis
));
}
// Convert chunk position -> global voxel origin
public Vector3I ToGlobalOrigin()
{
return new Vector3I(
Value.X * TChunk.Size,
Value.Y * TChunk.Size,
Value.Z * TChunk.Size
);
}
// Convert global voxel position -> local position inside this chunk
public static VoxelPos<TChunk> ToLocal(Vector3I globalPos) => ToLocal(globalPos.X, globalPos.Y, globalPos.Z);
// public static VoxelPos<TChunk> ToLocal(int x, int y, int z)
// {
// return new VoxelPos<TChunk>(
// Mod(x, TChunk.Size),
// Mod(y, TChunk.Size),
// Mod(z, TChunk.Size)
// );
// }
public static VoxelPos<TChunk> ToLocal(int x, int y, int z)
{
int mask = TChunk.Size - 1;
return new VoxelPos<TChunk>(
x & mask,
y & mask,
z & mask);
}
// Bounds check
// public bool IsInBounds(Vector3I localPos)
// {
// return localPos.X >= 0 && localPos.X < TChunk.Size &&
// localPos.Y >= 0 && localPos.Y < TChunk.Size &&
// localPos.Z >= 0 && localPos.Z < TChunk.Size;
// }
// Cast to Vector3I
public static implicit operator Vector3I(ChunkPos<TChunk> pos) => pos.Value;
// Equality
public bool Equals(ChunkPos<TChunk> other) => Value == other.Value;
public override bool Equals(object? obj) => obj is ChunkPos<TChunk> other && Equals(other);
public override int GetHashCode() => Value.GetHashCode();
public override string ToString() => $"ChunkPos({Value.X}, {Value.Y}, {Value.Z})";
// --- Casting operators ---
public static explicit operator ChunkPos<TChunk>(Vector3I v) => new ChunkPos<TChunk>(v);
// --- Arithmetic helpers (optional) ---
public static ChunkPos<TChunk> operator +(ChunkPos<TChunk> a, ChunkPos<TChunk> b) => new ChunkPos<TChunk>(a.Value + b.Value);
public static ChunkPos<TChunk> operator -(ChunkPos<TChunk> a, ChunkPos<TChunk> b) => new ChunkPos<TChunk>(a.Value - b.Value);
// Proper mod to handle negatives
private static int Mod(int a, int b) => (a % b + b) % b;
private const int BitsPerAxis = 21;
private const int BitsXZAxis = 21;
private const int BitsYAxis = 21;
private const int BitsDimension = 10;
private const long Mask = (1L << BitsPerAxis) - 1;
private const int OffsetY = BitsPerAxis;
private const int OffsetZ = BitsPerAxis * 2;
private const int Bias = 1 << (BitsPerAxis - 1);
public static int MinWidth{get;} = (int)-(1L << (BitsXZAxis - 1));
public static int MaxWidth{get;} = (int)(1L << (BitsXZAxis - 1)) - 1;
public static int MinHeight{get;} = (int)-(1L << (BitsYAxis - 1));
public static int MaxHeight{get;} = (int)(1L << (BitsYAxis - 1)) - 1;
public static int MinDepth{get;} = MinWidth;
public static int MaxDepth{get;} = MaxWidth;
public long ToKey()
{
long x = (long)(X + Bias) & Mask;
long y = (long)(Y + Bias) & Mask;
long z = (long)(Z + Bias) & Mask;
return (z << OffsetZ) | (y << OffsetY) | x;
}
public static ChunkPos<TChunk> FromKey(long key)
{
int x = (int)((key & Mask) - Bias);
int y = (int)(((key >> OffsetY) & Mask) - Bias);
int z = (int)(((key >> OffsetZ) & Mask) - Bias);
return new ChunkPos<TChunk>(x, y, z);
}
public static ChunkPos<TChunk> Create(int x, int y, int z)
{
return new ChunkPos<TChunk>(x, y, z);
}
// public bool Equals(IChunkPos other)=> X==other.X && Y == other.Y && Z == other.Z;
}
public interface IChunkSize<TSelf>
where TSelf : IChunkSize<TSelf>
{
static abstract int BitsPerAxis { get; }
static abstract int Size { get; }
static int SizeSquared => TSelf.Size * TSelf.Size;
static int SizeCubed => TSelf.Size * TSelf.Size * TSelf.Size;
static bool IsInBounds(Vector3I position)=>
position.X >= 0 && position.X < TSelf.Size
&& position.Y >= 0 && position.Y < TSelf.Size
&& position.Z >= 0 && position.Z < TSelf.Size;
}
public readonly struct Chunk16 : IChunkSize<Chunk16>
{
public static int BitsPerAxis => 4; // 2^5 = 32
public static int Size => 16;
}
public readonly struct Chunk32 : IChunkSize<Chunk32>
{
public static int BitsPerAxis => 5; // 2^5 = 32
public static int Size => 32;
}
public readonly struct Chunk64 : IChunkSize<Chunk64>
{
public static int BitsPerAxis => 6; // 2^6 = 64
public static int Size => 64;
}
public readonly struct Chunk256 : IChunkSize<Chunk256>
{
public static int BitsPerAxis => 8; // 2^6 = 64
public static int Size => 256;
}
public readonly struct VoxelPos : IEquatable<VoxelPos>
{
public readonly int X;
public readonly int Y;
public readonly int Z;
public ChunkPos<TChunk> GetChunkPos<TChunk>() where TChunk : IChunkSize<TChunk> => ChunkPos<TChunk>.FromGlobal(X, Y, Z);
public VoxelPos<TChunk> GetVoxelPos<TChunk>() where TChunk : IChunkSize<TChunk> => ChunkPos<TChunk>.ToLocal(X, Y, Z);
public bool Equals(VoxelPos other) => !(X != other.X|| Y != other.Y|| Z != other.Z);
public static VoxelPos operator +(VoxelPos a, VoxelPos b) => new VoxelPos(a.X + b.X, a.Y + b.Y, a.Z + b.Z);
public static VoxelPos operator -(VoxelPos a, VoxelPos b) => new VoxelPos(a.X - b.X, a.Y - b.Y, a.Z - b.Z);
public static implicit operator VoxelPos(Vector3I pos) => new VoxelPos(pos.X,pos.Y,pos.Z);
public static explicit operator Vector3I(VoxelPos pos) => new Vector3I(pos.X,pos.Y,pos.Z);
public VoxelPos(int x, int y, int z)
{
X = x;
Y = y;
Z = z;
}
public VoxelPos()
{
X = 0;
Y = 0;
Z = 0;
}
public override string ToString() => $"({X},{Y},{Z})";
public override int GetHashCode()=> HashCode.Combine(X,Y,Z);
}
public readonly struct VoxelPos<TChunk>
where TChunk : IChunkSize<TChunk>
{
private readonly uint _packed;
[MethodImpl(MethodImplOptions.AggressiveInlining)]//TODO see if atualt preformace increase
public VoxelPos(int x, int y, int z)
{
if ((uint)x >= TChunk.Size || (uint)y >= TChunk.Size || (uint)z >= TChunk.Size)
throw new ArgumentOutOfRangeException("Voxel position must be inside chunk bounds");//TODO check if negitive case are handled
int bits = TChunk.BitsPerAxis;
int mask = (1 << bits) - 1;
_packed = (uint)((x & mask) | ((y & mask) << bits) | ((z & mask) << (bits * 2)));
}
public VoxelPos(uint index)
{
if (index >= TChunk.Size * TChunk.Size * TChunk.Size)
throw new ArgumentOutOfRangeException("Voxel position must be inside chunk bounds");//TODO check if negitive case are handled
_packed = index;
}
public static bool TryCreateVoxelPos(VoxelPos pos, [NotNullWhen(true)] out VoxelPos<TChunk>? voxelPos)
{
if ((uint)pos.X < TChunk.Size || (uint)pos.Y < TChunk.Size || (uint)pos.Z < TChunk.Size)
{
voxelPos = new VoxelPos<TChunk>(pos.X,pos.Y,pos.Z);
return true;
}
voxelPos = null;
return false;
}
public VoxelPos ToGlobal(ChunkPos<TChunk> chunkPos) => chunkPos.ToGlobalOrigin() + new VoxelPos(X,Y,Z);
public (ChunkPos<TChunk> ChunkPos, VoxelPos<TChunk> VoxelPos) Offset(Vector3I offset, ChunkPos<TChunk> currentChunk)
{
int nx = X;
int ny = Y;
int nz = Z;
int cx = currentChunk.X;
int cy = currentChunk.Y;
int cz = currentChunk.Z;
nx += offset.X;
ny += offset.Y;
nz += offset.Z;
// X
if (nx < 0 || nx >= TChunk.Size)
{
int dx = Math.DivRem(nx, TChunk.Size, out nx); // fast div + remainder
cx += dx;
}
// Y
if (ny < 0 || ny >= TChunk.Size)
{
int dy = Math.DivRem(ny, TChunk.Size, out ny);
cy += dy;
}
// Z
if (nz < 0 || nz >= TChunk.Size)
{
int dz = Math.DivRem(nz, TChunk.Size, out nz);
cz += dz;
}
return (new ChunkPos<TChunk>(cx, cy, cz), new VoxelPos<TChunk>(nx, ny, nz));
}
// public VoxelPos(uint packed) => _packed = packed;
public uint Index => _packed;
private static int Bits => TChunk.BitsPerAxis;
private static int Mask => (1 << Bits) - 1;
public int X => (int)(_packed & Mask);
public int Y => (int)((_packed >> Bits) & Mask);
public int Z => (int)((_packed >> (Bits * 2)) & Mask);
public override string ToString() => $"({X}, {Y}, {Z}) idx={Index}";
public override int GetHashCode() => _packed.GetHashCode();
// Implicit conversion to raw index
public static explicit operator uint(VoxelPos<TChunk> pos) => pos._packed;
public static implicit operator Vector3I(VoxelPos<TChunk> pos) => new Vector3I(pos.X, pos.Y, pos.Z);
public static explicit operator VoxelPos(VoxelPos<TChunk> pos) => new Vector3I(pos.X, pos.Y, pos.Z);
public static VoxelPos<TChunk> operator +(VoxelPos<TChunk> a, Vector3I b) =>
new VoxelPos<TChunk>(a.X + b.X, a.Y + b.Y, a.Z + b.Z);
public static VoxelPos<TChunk> operator -(VoxelPos<TChunk> a, Vector3I b) =>
new VoxelPos<TChunk>(a.X - b.X, a.Y - b.Y, a.Z - b.Z);
}

View File

@@ -0,0 +1 @@
uid://dlgeisueoqh40

View File

@@ -0,0 +1,193 @@
using System.Collections.Generic;
using Godot;
using SJK.Functional;
using System.Linq;
namespace SJK.Voxels;
public interface IVoxelChunk<TVoxel> : IReadOnlyVoxelChunk<TVoxel>
{
void SetVoxel(VoxelPos voxelPos, TVoxel voxel);
virtual void Fill(TVoxel voxel)
{
for (int x = Bounds.Min.X; x < this.Bounds.Max.X; x++)
{
for (int y = Bounds.Min.Y; y < this.Bounds.Max.Y; y++)
{
for (int z = Bounds.Min.Z; z < Bounds.Max.Z; z++)
{
SetVoxel(new VoxelPos(x,y,z),voxel);
}
}
}
}
}
public interface IVoxelChunk<TVoxel, TChunk> : IVoxelChunk<TVoxel>, IReadOnlyVoxelChunk<TVoxel,TChunk> where TChunk : IChunkSize<TChunk>
{
void SetVoxel(VoxelPos<TChunk> position, TVoxel voxel);
}
// public interface IVoxelChunk : IReadOnlyVoxelChunk
// {
// }
// public interface IReadOnlyVoxelChunk
// {
// }
public interface IReadOnlyVoxelChunk<TVoxel>// : IReadOnlyVoxelChunk
{
TVoxel GetVoxel(VoxelPos voxelPos);
VoxelChunkEnumerator<TVoxel> GetEnumerator() => new VoxelChunkEnumerator<TVoxel>(this);
virtual bool ContainsPos(VoxelPos position) => Bounds.IsInBounds((Vector3I)position);
VoxelBounds Bounds {get;}
}
public readonly record struct VoxelBounds(Vector3I Min, Vector3I Max)
{
public Vector3I Size => Max-Min;
public bool IsInBounds(Vector3I pos) => !(pos.X < Min.X || pos.Y < Min.Y || pos.Z < Min.Z || pos.X > Max.X || pos.Y > Max.Y || pos.Z > Max.Z);
}
public interface IReadOnlyVoxelChunk<TVoxel, TChunk> : IReadOnlyVoxelChunk<TVoxel> where TChunk : IChunkSize<TChunk>
{
TVoxel GetVoxel(VoxelPos<TChunk> position);
bool IsInBounds(Vector3I position) => IChunkSize<TChunk>.IsInBounds(position);
virtual bool ContainsPos(VoxelPos<TChunk> position) => Bounds.IsInBounds((Vector3I)position);
new VoxelChunkEnumerator<TVoxel,TChunk> GetEnumerator() => new VoxelChunkEnumerator<TVoxel,TChunk>(this);
}
public static class VoxelExtensions{
public static void SetVoxel<TVoxel>(this IVoxelChunk<TVoxel> voxelChunk, int x, int y, int z, TVoxel value) =>voxelChunk.SetVoxel(new VoxelPos(x,y,z),value);
public static TVoxel GetVoxel<TVoxel>(this IVoxelChunk<TVoxel> voxelChunk, int x, int y, int z) =>voxelChunk.GetVoxel(new VoxelPos(x,y,z));
}
public interface ILayerMap<TVoxel> {
bool TryGetChunk(Vector3I chunkPos, out IVoxelChunk<TVoxel> chunk); // Untyped
void SetChunk(Vector3I chunkPos, IVoxelChunk<TVoxel> chunk);
}
public class LayerMap<TVoxel> : ILayerMap<TVoxel> {
private readonly Dictionary<Vector3I, IVoxelChunk<TVoxel>> chunks = new();
public bool TryGetChunk(Vector3I chunkPos, out IVoxelChunk<TVoxel> chunk)
{
var r =chunks.TryGetValue(chunkPos, out var result);
chunk = result;
return r;
}
public void SetChunk(Vector3I chunkPos, IVoxelChunk<TVoxel> chunk) =>
chunks[chunkPos] = chunk;
}
public interface IWorldWrapper
{
Vector3I Wrap(Vector3I position);
Vector3I GetBounds();
AxisMode AxisX { get; }
AxisMode AxisY {get;}
AxisMode AxisZ {get;}
bool IsInsideBounds(Vector3I position);
enum AxisMode : byte { Constrined, Infinate, Wrapping }
}
public sealed class LimitedSizeWorld(Vector3I size) : IWorldWrapper
{
private readonly Vector3I size = size;
public IWorldWrapper.AxisMode AxisX => IWorldWrapper.AxisMode.Constrined;
public IWorldWrapper.AxisMode AxisY => IWorldWrapper.AxisMode.Constrined;
public IWorldWrapper.AxisMode AxisZ => IWorldWrapper.AxisMode.Constrined;
public Vector3I GetBounds() => size;
public bool IsInsideBounds(Vector3I position) =>
position.X >= 0 && position.X < size.X &&
position.Y >= 0 && position.Y < size.Y &&
position.Z >= 0 && position.Z < size.Z;
public Vector3I Wrap(Vector3I position)
{
return position;
}
}
public sealed class EndlessSizeXZWorld(int maxHeight) : IWorldWrapper
{
public int MaxHeight { get; } = maxHeight;
public IWorldWrapper.AxisMode AxisX => IWorldWrapper.AxisMode.Infinate;
public IWorldWrapper.AxisMode AxisY => IWorldWrapper.AxisMode.Constrined;
public IWorldWrapper.AxisMode AxisZ => IWorldWrapper.AxisMode.Infinate;
public Vector3I Wrap(Vector3I position)
=> position;
public Vector3I GetBounds()
=> new Vector3I(0, MaxHeight, 0);
public bool IsInsideBounds(Vector3I position) => position.Y >= 0 && position.Y < MaxHeight;
}
public sealed class ToroidalWrapping(Vector3I size) : IWorldWrapper
{
private readonly Vector3I size = size;
public IWorldWrapper.AxisMode AxisX => IWorldWrapper.AxisMode.Wrapping;
public IWorldWrapper.AxisMode AxisY => IWorldWrapper.AxisMode.Constrined;
public IWorldWrapper.AxisMode AxisZ => IWorldWrapper.AxisMode.Wrapping;
public Vector3I GetBounds() => size;
public Vector3I Wrap(Vector3I position) => new Vector3I(Mod(position.X, size.X), position.Y, Mod(position.Z, size.Z));
public bool IsInsideBounds(Vector3I position) => position.Y >= 0 && position.Y < size.Y;
private int Mod(int a, int b)
{
int r = a % b;
return r < 0 ? r + b : r;
}
}
public sealed class MirroredZWrappingXOffset(Vector3I size) : IWorldWrapper
{
private readonly Vector3I size = size;
private readonly int halfX = size.X / 2;
public IWorldWrapper.AxisMode AxisX => IWorldWrapper.AxisMode.Wrapping;
public IWorldWrapper.AxisMode AxisY => IWorldWrapper.AxisMode.Constrined;
public IWorldWrapper.AxisMode AxisZ => IWorldWrapper.AxisMode.Wrapping;
public Vector3I GetBounds() => size;
public Vector3I Wrap(Vector3I position)
{
var result = TwistedWrapInt(new Vector2I(position.X, position.Z), size.X, size.Z);
return new(result.local.X, position.Y, result.local.Y);
}
private (Vector2I local, bool flipped) TwistedWrapInt(Vector2I global, int W, int H)
{
int gx = global.X;
int gy = global.Y;
int yWrap = FloorDiv(gy, H);
bool flip = (yWrap & 1) != 0;
int tileY = gy % H;
if (tileY < 0) tileY += H;
int ly = flip ? (H - 1 - tileY) : tileY;
int xOffset = flip ? W / 2 : 0;
int wrappedX = gx + xOffset;
int tileX = wrappedX % W;
if (tileX < 0) tileX += W;
int lx = flip ? (W - 1 - tileX) : tileX;
return (new Vector2I(lx, ly), flip);
}
private int FloorDiv(int a, int b)
{
return (a >= 0) ? a / b : ((a + 1) / b) - 1;
}
public bool IsInsideBounds(Vector3I position) => position.Y >= 0 && position.Y < size.Y;
}

View File

@@ -0,0 +1 @@
uid://bn4dkifwcftqm

View File

@@ -0,0 +1,316 @@
// https://github.com/MrAlvaroRamirez/Unity-SurfaceNet-VoxelTerrain/blob/main/Assets/Scripts/Data/ChunkMeshBuilder.cs
using System;
using System.Collections.Generic;
using Godot;
using SJK.Voxels;
public static class ChunkMeshBuilder
{
private static bool IsVoid<TVoxel>(Vector3I pos, IReadOnlyVoxelChunk<TVoxel> voxels,Func<TVoxel,bool> isVoid)
{
if (voxels.Bounds.IsInBounds(pos))
return isVoid(voxels.GetVoxel(pos));
return false;
// if (voxels[pos.x + (pos.z * Constants.CHUNK_SIZE) + (pos.y * Constants.CHUNK_AREA)] == 0)
// {
// return true;
// }
return false;
}
public static ChunkMeshData BuildChunkMesh<TVoxel>(
IReadOnlyVoxelChunk<TVoxel> voxels,
// Vector3I chunkPos,
Func<TVoxel,bool> isVoid,
Func<TVoxel, Direction,(Color,int)> faceUvs
// IVoxelWorld<TVoxel,IVoxelChunk<TVoxel>> world
)
{
ChunkMeshData meshData = new ChunkMeshData();
meshData.ClearData();
int counter = 0;
// Iterate through all voxels in the chunk
// We start from -1 in order to also check the quads on the edges of the chunk
for (int x = voxels.Bounds.Min.X; x < voxels.Bounds.Max.X ; x++)
{
for (int y = voxels.Bounds.Min.Y; y < voxels.Bounds.Max.Y ; y++)
{
for (int z = voxels.Bounds.Min.Z; z < voxels.Bounds.Max.Z ; z++)
{
Vector3I localPos = new Vector3I(x, y, z);
// TODO? Instead of passing all this data, we can make the method local
CreateSurfaceQuads(localPos, voxels, meshData, ref counter, isVoid, faceUvs);
}
}
}
return meshData;
}
// This function will iterate through all the positive axis directions of a voxel
// If it finds a voxel that is not the same as the current voxel, it will create a quad
private static void CreateSurfaceQuads<TVoxel>(
Vector3I localPos,
IReadOnlyVoxelChunk<TVoxel> voxels,
ChunkMeshData meshData,
ref int counter,
Func<TVoxel,bool> isVoid,
Func<TVoxel, Direction,(Color,int)> faceUvs
)
{
for (int i = 0; i < voxelAxis.Length; i++)
{
Vector3I axis = voxelAxis[i];
Vector3I neighborPos = localPos + axis;
bool voxelExists = !IsVoid(localPos, voxels,isVoid);
bool neighboorExists = !IsVoid(neighborPos, voxels,isVoid);
// We need to handle quad direction differently for each case
if ((!voxelExists && neighboorExists) || (voxelExists && !neighboorExists))
{
Vector3[] quadVertices = AddQuad(localPos, i, voxels, isVoid);
var color = faceUvs(voxels.GetVoxel(!voxelExists?localPos:neighborPos), (Direction)i);
// Add vertices and triangles to the mesh data
for (int j = 0; j < 6; j++)
{
// Split the quad into two triangles
if (voxelExists)
meshData.vertices.Add(quadVertices[voxelTriangles[j]]);
else
meshData.vertices.Add(quadVertices[voxelTrianglesReverse[j]]);
meshData.uvs.Add(voxelUVs[voxelTriangles[j]]);
meshData.uvs2.Add(new Vector2(color.Item2,0));
meshData.colors.Add(color.Item1);
meshData.triangles.Add(counter++);
}
}
}
}
private static Vector3[] AddQuad<TVoxel>(Vector3I localPos, int axis, IReadOnlyVoxelChunk<TVoxel> voxels,Func<TVoxel,bool> isVoid)
{
Vector3[] vertices = new Vector3[4];
for (int i = 0; i < voxelQuads[axis].Length; i++)
{
Vector3I quadVertexPos = localPos + voxelQuads[axis][i];
vertices[i] = AverageVertex(quadVertexPos, voxels,isVoid);
// To disable smoothing, comment the line above and add this one:
// vertices[i] = quadVertexPos;
}
return vertices;
}
// Naive surface net implementation
private static Vector3 AverageVertex<TVoxel>(Vector3I pos, IReadOnlyVoxelChunk<TVoxel> voxels,Func<TVoxel,bool> isVoid)
{
Vector3 total = Vector3.Zero;
int count = 0;
// Iterate over the edges of the current voxel
// For each edge we will check if the voxel at the end of the edge is filled or not
// We will calculate the average of all these values to estimate the position of the vertex
// As a result, the final mesh will be smoothed
for (int i = 0; i < 12; i++)
{
Vector3I edgePos = pos + edgeOffsets[i, 0];
Vector3I edgeNeighborPos = pos + edgeOffsets[i, 1];
if (IsVoid(edgePos, voxels,isVoid) != IsVoid(edgeNeighborPos, voxels,isVoid))
{
total += (Vector3)(edgePos + edgeNeighborPos) / 2f;
count++;
}
}
return total / count;
}
#region Voxel Statics
// Constant for the positive axis directions
static readonly Vector3I[] voxelAxis = new Vector3I[3]
{
new Vector3I(1, 0, 0),
new Vector3I(0, 1, 0),
new Vector3I(0, 0, 1),
};
// Vertex index lookup table for forming quads
static readonly Vector3I[][] voxelQuads = new Vector3I[3][]
{
new Vector3I[4]
{
new Vector3I(0, 0, -1),
new Vector3I(0, -1, -1),
new Vector3I(0, -1, 0),
new Vector3I(0, 0, 0)
},
new Vector3I[4]
{
new Vector3I(0, 0, -1),
new Vector3I(0, 0, 0),
new Vector3I(-1, 0, 0),
new Vector3I(-1, 0, -1)
},
new Vector3I[4]
{
new Vector3I(0, 0, 0),
new Vector3I(0, -1, 0),
new Vector3I(-1, -1, 0),
new Vector3I(-1, 0, 0)
},
};
static readonly Vector3I[,] edgeOffsets = new Vector3I[12, 2]
{
// Edges on min Z axis
{ new Vector3I(0, 0, 0), new Vector3I(1, 0, 0) },
{ new Vector3I(1, 0, 0), new Vector3I(1, 1, 0) },
{ new Vector3I(1, 1, 0), new Vector3I(0, 1, 0) },
{ new Vector3I(0, 1, 0), new Vector3I(0, 0, 0) },
// Edges on max Z axisCreateSurfaceQuads
{ new Vector3I(0, 0, 1), new Vector3I(1, 0, 1) },
{ new Vector3I(1, 0, 1), new Vector3I(1, 1, 1) },
{ new Vector3I(1, 1, 1), new Vector3I(0, 1, 1) },
{ new Vector3I(0, 1, 1), new Vector3I(0, 0, 1) },
// Edges connecting min Z to max Z
{ new Vector3I(0, 0, 0), new Vector3I(0, 0, 1) },
{ new Vector3I(1, 0, 0), new Vector3I(1, 0, 1) },
{ new Vector3I(1, 1, 0), new Vector3I(1, 1, 1) },
{ new Vector3I(0, 1, 0), new Vector3I(0, 1, 1) }
};
static readonly Vector2[] voxelUVs = new Vector2[4]
{
new Vector2(0, 0),
new Vector2(1, 0),
new Vector2(1, 1),
new Vector2(1, 0)
};
static readonly int[] voxelTriangles = new int[] { 0, 3, 2, 0, 2, 1 };
static readonly int[] voxelTrianglesReverse = new int[] { 0, 2, 3, 0, 1, 2 };
#endregion
public class ChunkMeshData
{
// public Mesh mesh;
public List<Vector3> vertices;
public List<int> triangles;
public List<Color> colors;
public List<Vector2> uvs;
public List<Vector2> uvs2;
public bool initialized;
public void ClearData()
{
if (!initialized)
{
vertices = new List<Vector3>();
triangles = new List<int>();
uvs = new List<Vector2>();
uvs2 = new List<Vector2>();
colors = new List<Color>();
initialized = true;
// mesh = new Mesh();
}
else
{
vertices.Clear();
triangles.Clear();
uvs.Clear();
uvs2.Clear();
colors.Clear();
}
}
public Mesh UploadMesh(bool sharedVertices = false)
{
if (vertices.Count == 0)
{
return new BoxMesh();
}
Godot.Collections.Array surfaceArray = [];
surfaceArray.Resize((int)Mesh.ArrayType.Max);
surfaceArray[(int)Mesh.ArrayType.Vertex] = vertices.ToArray();
surfaceArray[(int)Mesh.ArrayType.TexUV] = uvs.ToArray();
surfaceArray[(int)Mesh.ArrayType.TexUV2] = uvs2.ToArray();
surfaceArray[(int)Mesh.ArrayType.Color] = colors.ToArray();
surfaceArray[(int)Mesh.ArrayType.Normal] = new Vector3[vertices.Count];
surfaceArray[(int)Mesh.ArrayType.Index] = triangles.ToArray();
var arrMesh = new ArrayMesh();
// No blendshapes, lods, or compression used.
arrMesh.AddSurfaceFromArrays(Mesh.PrimitiveType.Triangles, surfaceArray);
MeshDataTool meshDataTool = new MeshDataTool();
meshDataTool.CreateFromSurface(arrMesh, 0);
for (int i = 0; i < meshDataTool.GetFaceCount(); i++)
{
// # Get the index in the vertex array.
var a = meshDataTool.GetFaceVertex(i, 0);
var b = meshDataTool.GetFaceVertex(i, 1);
var c = meshDataTool.GetFaceVertex(i, 2);
// # Get vertex position using vertex index.
var ap = meshDataTool.GetVertex(a);
var bp = meshDataTool.GetVertex(b);
var cp = meshDataTool.GetVertex(c);
// # Calculate face normal.
Vector3 nn = (bp - cp).Cross(ap - bp).Normalized();
// # Add face normal to current vertex normal.
// # This will not result in perfect normals, but it will be close.
meshDataTool.SetVertexNormal(a, nn + meshDataTool.GetVertexNormal(a));
meshDataTool.SetVertexNormal(b, nn + meshDataTool.GetVertexNormal(b));
meshDataTool.SetVertexNormal(c, nn + meshDataTool.GetVertexNormal(c));
}
for (int i = 0; i < meshDataTool.GetVertexCount(); i++)
{
var v = meshDataTool.GetVertexNormal(i).Normalized();
meshDataTool.SetVertexNormal(i, v);
}
arrMesh.ClearSurfaces();
meshDataTool.CommitToSurface(arrMesh);
return arrMesh;
// if (sharedVertices)
// {
// Dictionary<Vector3, int> vertexIndices =
// new Dictionary<Vector3, int>();
// List<int> sharedTriangles = new List<int>();
// for (int i = 0; i < vertices.Count; i++)
// {
// var key = vertices[i];
// if (!vertexIndices.ContainsKey(key))
// {
// vertexIndices.Add(key, vertexIndices.Count);
// }
// sharedTriangles.Add(vertexIndices[key]);
// }
// mesh.SetVertices(vertexIndices.Keys.ToList());
// mesh.SetTriangles(sharedTriangles, 0, false);
// }
// else
// {
// mesh.SetVertices(vertices);
// mesh.SetTriangles(triangles, 0, false);
// mesh.SetUVs(0, uvs);
// mesh.SetColors(colors);
// }
// mesh.Optimize();
// mesh.RecalculateNormals();
// mesh.RecalculateBounds();voxelRegister.GetVoxel(voxel).GetAttribute(()=> new TextureCube(Colors.White)).Color
// mesh.UploadMeshData(false);
}
}
}

View File

@@ -0,0 +1 @@
uid://bd586qflhhi13

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
uid://b13215rwdt03o

View File

@@ -0,0 +1 @@
uid://dh21dhxsoadt4

View File

@@ -0,0 +1,224 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Godot;
namespace SJK.Voxels;
#nullable enable
public class NonePowerTwoOctree<TVoxel> : IVoxelChunk<TVoxel>
{
public Vector3I Size { get; }
public VoxelBounds Bounds => new VoxelBounds(Vector3I.Zero,Size);
private OctreeNode<TVoxel> root;
public NonePowerTwoOctree(Vector3I size, TVoxel @default)
{
Size = size;
var extent = SJK.Math.SJKMath.UpperPowerOfTwo(SJK.Math.SJKMath.Max(size.ToArrayXYZ()));
root = new OctreeNode<TVoxel>(Vector3I.Zero, extent, 1, @default);
}
public VoxelChunkEnumerator<TVoxel> GetEnumerator()
{
throw new NotImplementedException();
}
public TVoxel GetVoxel(VoxelPos position) => root.GetVoxel(position);
public bool IsInBounds(VoxelPos position) =>
position.X >= 0 && position.X < Size.X
&& position.Y >= 0 && position.Y < Size.Y
&& position.Z >= 0 && position.Z < Size.Z;
public void SetVoxel(VoxelPos position, TVoxel voxel) => root.SetVoxel(position, voxel);
public void SetVoxel(VoxelPos position, object voxel)
{
if (voxel is TVoxel voxel1)
root.SetVoxel(position,voxel1);
}
}
public class Octree<TVoxel,TChunk> : IVoxelChunk<TVoxel,TChunk>, IReadOnlyVoxelChunk<TVoxel,TChunk> where TChunk : IChunkSize<TChunk>
{
public Vector3I Size => new Vector3I(TChunk.Size, TChunk.Size, TChunk.Size);
public VoxelBounds Bounds => new(Vector3I.Zero,Size);
private OctreeNode<TVoxel> root;
public Octree(TVoxel @default)
{
root = new OctreeNode<TVoxel>(Vector3I.Zero, TChunk.Size, 1, @default);
}
public VoxelChunkEnumerator<TVoxel> GetEnumerator() => new VoxelChunkEnumerator<TVoxel>(this);
public TVoxel GetVoxel(VoxelPos position) => root.GetVoxel(position);
public bool IsInBounds(VoxelPos position) =>
position.X >= 0 && position.X < Size.X
&& position.Y >= 0 && position.Y < Size.Y
&& position.Z >= 0 && position.Z < Size.Z;
public void SetVoxel(VoxelPos position, TVoxel voxel) => root.SetVoxel(position, voxel);
public OctreeNode<TVoxel> GetRoot() => root;
public TVoxel GetVoxel(VoxelPos<TChunk> position)=> GetVoxel(new VoxelPos(position.X,position.Y,position.Z));
public void SetVoxel(VoxelPos<TChunk> position, TVoxel voxel)=> SetVoxel(new VoxelPos(position.X,position.Y,position.Z),voxel);
VoxelChunkEnumerator<TVoxel, TChunk> IReadOnlyVoxelChunk<TVoxel, TChunk>.GetEnumerator()
{
return new VoxelChunkEnumerator<TVoxel, TChunk>(this);
}
}
public struct OctreeNode<TVoxel>
{
public TVoxel? Value;
public OctreeNode<TVoxel>[]? ChildNodes = null;
private readonly Vector3I origin;
private readonly int size;
private readonly int minResolution;
public OctreeNode(Vector3I origin, int size, int minResolution, TVoxel @default)
{
this.origin = origin;
this.size = size;
this.minResolution = minResolution;
Value = @default;
}
[MemberNotNullWhen(true, nameof(Value))]
[MemberNotNullWhen(false, nameof(ChildNodes))]
public bool IsLeaf()
{
return ChildNodes is null;
}
public TVoxel GetVoxel(VoxelPos pos)
{
if (IsLeaf())
{
return Value;
}
int half = size / 2;
int idx = ChildIndex(pos, half);
return ChildNodes[idx].GetVoxel(pos);
}
public void SetVoxel(VoxelPos pos, TVoxel voxel)
{
if (size <= minResolution)
{
ChildNodes = null;
Value = voxel;
return;
}
if (ChildNodes is null)
Subdivide();
int half = size / 2;
int idx = ChildIndex(pos, half);
ChildNodes[idx].SetVoxel(pos, voxel);
// Check if all children are uniform → collapse
TVoxel first = ChildNodes[0].GetVoxel(origin);
bool uniform = true;
for (int i = 1; i < 8; i++)
{
if (!EqualityComparer<TVoxel>.Default.Equals(first, ChildNodes[i].GetVoxel(origin)))
{
uniform = false;
break;
}
}
if (uniform)
{
Value = first;
ChildNodes = null;
}
}
public void Trim()
{
if (ChildNodes == null) return;
foreach (var child in ChildNodes)
child.Trim();
// Try collapse again
TVoxel first = ChildNodes[0].Value!;
bool uniform = true;
for (int i = 1; i < 8; i++)
{
if (!EqualityComparer<TVoxel>.Default.Equals(first, ChildNodes[i].Value))
{
uniform = false;
break;
}
}
if (uniform)
{
Value = first;
ChildNodes = null;
}
}
[MemberNotNull(nameof(ChildNodes))]
private void Subdivide()
{
ChildNodes = new OctreeNode<TVoxel>[8];
int half = size / 2;
for (int dx = 0; dx < 2; dx++)
{
for (int dy = 0; dy < 2; dy++)
{
for (int dz = 0; dz < 2; dz++)
{
int idx = (dx << 2) | (dy << 1) | dz;
Vector3I childOrigin = origin + new Vector3I(dx * half, dy * half, dz * half);
ChildNodes[idx] = new OctreeNode<TVoxel>(childOrigin, half, minResolution, Value);
}
}
}
Value = default;
}
public int ChildIndex(VoxelPos pos, int half)
{
int dx = (pos.X - origin.X) >= half ? 1 : 0;
int dy = (pos.Y - origin.Y) >= half ? 1 : 0;
int dz = (pos.Z - origin.Z) >= half ? 1 : 0;
return (dx << 2) | (dy << 1) | dz;
}
public void TraverseBounds(Vector3 origin, Action<Vector3, int,bool> visitor)
{
visitor(origin, size,IsLeaf());
if (!IsLeaf())
{
int half = size / 2;
for (int i = 0; i < 8; i++)
{
var child = ChildNodes[i];
Vector3 childOrigin = origin + new Vector3(
(i & 1) != 0 ? half : 0,
(i & 2) != 0 ? half : 0,
(i & 4) != 0 ? half : 0);
child.TraverseBounds(childOrigin, visitor);
}
}
}
}

View File

@@ -0,0 +1 @@
uid://ctjgia5dlt8xf

View File

@@ -0,0 +1,58 @@
using Godot;
using System;
using System.Collections.Generic;
using System.Diagnostics;
namespace SJK.Voxels;
public sealed class SparseVoxelChunk<TVoxel, TChunk> : IVoxelChunk<TVoxel,TChunk> where TChunk : IChunkSize<TChunk> where TVoxel : IEquatable<TVoxel>
{
// public Vector3I Size => new Vector3I(TChunk.Size, TChunk.Size, TChunk.Size);
private Dictionary<VoxelPos<TChunk>, TVoxel> _data;
private readonly TVoxel _default;
public VoxelBounds Bounds => new(Vector3I.Zero,new(TChunk.Size,TChunk.Size,TChunk.Size));
public SparseVoxelChunk(TVoxel @defualt = default)
{
_data = new Dictionary<VoxelPos<TChunk>, TVoxel>();
this._default = defualt;
}
public void SetVoxel(VoxelPos position, TVoxel voxel) => SetVoxel(new VoxelPos<TChunk>(position.X, position.Y, position.Z), voxel);
public TVoxel GetVoxel(VoxelPos position) => GetVoxel(new VoxelPos<TChunk>(position.X,position.Y,position.Z));
public VoxelChunkEnumerator<TVoxel> GetEnumerator() => new VoxelChunkEnumerator<TVoxel>(this);
public bool IsInBounds(VoxelPos position) =>
position.X >= 0 && position.X < TChunk.Size
&& position.Y >= 0 && position.Y < TChunk.Size
&& position.Z >= 0 && position.Z < TChunk.Size;
public void SetVoxel(VoxelPos<TChunk> position, TVoxel voxel)
{
// Debug.Assert(IsInBounds(position), $"{nameof(position)} is not in bounds with value of {position}, Size {TChunk.Size}");
if (voxel.Equals(_default))
{
_data.Remove(position);
return;
}
_data[position] = voxel;
}
public TVoxel GetVoxel(VoxelPos<TChunk> position)
{
if (_data.TryGetValue(position, out var voxel))
{
return voxel;
}
return _default;
}
VoxelChunkEnumerator<TVoxel, TChunk> IReadOnlyVoxelChunk<TVoxel, TChunk>.GetEnumerator()
{
return new VoxelChunkEnumerator<TVoxel, TChunk>(this);
}
}

View File

@@ -0,0 +1 @@
uid://btrlde2p7lphe

View File

@@ -0,0 +1,384 @@
using System;
using System.Collections.Generic;
using Godot;
public static class SurfaceNetMesher
{
/*
* Give each voxel a position, an index, a corner mask, and an edge mask.
* isOnSurface basically asks if our mesh goes through this voxel
*/
private struct Voxel
{
public int vertexIndex;
public Vector3 vertexPosition;
public bool isOnSurface;
public int voxelEdgeMask;
public int cornerMask;
}
private struct Sample
{
public Vector3 point;
public float value;
}
#region SurfaceNet Tables
// Define the edge table and cube edges with a set size
static private int[] edgeTable = new int[256];
static private int[] cubeEdges = new int[24];
static SurfaceNetMesher()
{
GenerateCubeEdgesTable();
GenerateIntersectionTable();
}
/*
* Set up the 8 corners of each voxel like this:
* Very important that this is set up the same way as the cube edges
*
* y z
* ^ /
* |
* 6----7
* /| /|
* 4----5 |
* | 2--|-3
* |/ |/
* 0----1 --> x
*
*/
private static Vector3[] voxelCornerOffsets = new Vector3[8] {
new Vector3(0,0,0), // 0
new Vector3(1,0,0), // 1
new Vector3(0,1,0), // 2
new Vector3(1,1,0), // 3
new Vector3(0,0,1), // 4
new Vector3(1,0,1), // 5
new Vector3(0,1,1), // 6
new Vector3(1,1,1) // 7
};
/*
* Build an intersection table. This is a 2^(cube config) -> 2^(edge config) map
* There is only one entry for each possible cube configuration
* and the output is a 12-bit vector enumerating all edges
* crossing the 0-level
*/
private static void GenerateIntersectionTable()
{
for (int i = 0; i < 256; ++i)
{
int em = 0;
for (int j = 0; j < 24; j += 2)
{
var a = Convert.ToBoolean(i & (1 << cubeEdges[j]));
var b = Convert.ToBoolean(i & (1 << cubeEdges[j + 1]));
em |= a != b ? (1 << (j >> 1)) : 0;
}
edgeTable[i] = em;
}
}
/*
* Utility function to build a table of possible edges for a cube with each
* pair of points representing one edge i.e. [0,1,0,2,0,4,...] would be the
* edges from points 0 to 1, 0 to 2, and 0 to 4 respectively:
*
* y z
* ^ /
* |
* 6----7
* /| /|
* 4----5 |
* | 2--|-3
* |/ |/
* 0----1 --> x
*
*/
private static void GenerateCubeEdgesTable()
{
int k = 0;
for (int i = 0; i < 8; ++i)
{
for (int j = 1; j <= 4; j <<= 1)
{
int p = i ^ j;
if (i <= p)
{
cubeEdges[k++] = i;
cubeEdges[k++] = p;
}
}
}
}
#endregion
public static Mesh CreateMesh(Vector3I size, Func<Vector3I,float> sampler,Func<Vector3I,bool> isCube)
{
var vertices = new List<Vector3>();
var triangles = new List<int>();
CalculateVertexPositions(size,vertices, out var voxels,sampler,isCube);
// set the size of our buffer
// var buffer = new int[4096];
// get the width, height, and depth of the sample space for our nested for loops
int width = size.X ;
int height = size.Y ;
int depth = size.Z ;
Vector3I pos = new ();
float[] grid = new float[8];
// resize the buffer if it's not big enough
// if (R[2] * 2 > buffer.Length)
// buffer = new int[R[2] * 2];
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
for (int z = 0; z < depth; z++)
{
pos = new(x, y, z);
// get the corner mask we calculated earlier
var mask = voxels[pos.X, pos.Y, pos.Z].cornerMask;
// Early Termination Check
if (mask == 0 || mask == 0xff)
{
continue;
}
// get edge mask
var edgeMask = edgeTable[mask];
var vertex = new Vector3();
var edgeIndex = 0;
//For Every Cube Edge
for (var i = 0; i < 12; i++)
{
//Use Edge Mask to Check if Crossed
if (!Convert.ToBoolean(edgeMask & (1 << i)))
{
continue;
}
//If So, Increment Edge Crossing #
edgeIndex++;
//Find Intersection Point
var e0 = cubeEdges[i << 1];
var e1 = cubeEdges[(i << 1) + 1];
var g0 = grid[e0];
var g1 = grid[e1];
var t = g0 - g1;
if (Math.Abs(t) > 1e-16)
t = g0 / t;
else
continue;
//Interpolate Vertices, Add Intersections
for (int j = 0, k = 1; j < 3; j++, k <<= 1)
{
var a = e0 & k;
var b = e1 & k;
if (a != b)
vertex[j] += Convert.ToBoolean(a) ? 1f - t : t;
else
vertex[j] += Convert.ToBoolean(a) ? 1f : 0;
}
}
//Average Edge Intersections, Add to Coordinate
var s = 1f / edgeIndex;
for (var i = 0; i < 3; i++)
{
vertex[i] = pos[i] + s * vertex[i];
}
vertex = voxels[pos[0], pos[1], pos[2]].vertexPosition;
//Add Faces (Loop Over 3 Base Components)
for (var i = 0; i < 3; i++)
{
//First 3 Entries Indicate Crossings on Edge
if (!Convert.ToBoolean(edgeMask & (1 << i)))
{
continue;
}
//i - Axes, iu, iv - Ortho Axes
var iu = (i + 1) % 3;
var iv = (i + 2) % 3;
//Skip if on Boundary
if (pos[iu] == 0 || pos[iv] == 0)
continue;
var a = GetVoxelVertexIndex(pos, i,voxels);
var b = GetVoxelVertexIndex(Offset(pos, iu, -1), i,voxels);
var c = GetVoxelVertexIndex(Offset(pos, iv, -1), i,voxels);
var d = GetVoxelVertexIndex(Offset(Offset(pos, iu, -1), iv, -1), i,voxels);
if (a < 0 || b < 0 || c < 0 || d < 0)
continue;
if ((mask & 1) != 0)
{
triangles.Add(a); triangles.Add(d); triangles.Add(b);
triangles.Add(a); triangles.Add(c); triangles.Add(d);
}
else
{
triangles.Add(a); triangles.Add(b); triangles.Add(d);
triangles.Add(a); triangles.Add(d); triangles.Add(c);
}
}
}
}
}
if (vertices.Count == 0)
{
return new BoxMesh();
}
Godot.Collections.Array surfaceArray = [];
surfaceArray.Resize((int)Mesh.ArrayType.Max);
surfaceArray[(int)Mesh.ArrayType.Vertex] = vertices.ToArray();
// surfaceArray[(int)Mesh.ArrayType.TexUV] = uvs.ToArray();
// surfaceArray[(int)Mesh.ArrayType.Normal] = new Vector3[vertices.Count];
surfaceArray[(int)Mesh.ArrayType.Index] = triangles.ToArray();
var arrMesh = new ArrayMesh();
// No blendshapes, lods, or compression used.
arrMesh.AddSurfaceFromArrays(Mesh.PrimitiveType.Triangles, surfaceArray);
MeshDataTool meshDataTool = new MeshDataTool();
meshDataTool.CreateFromSurface(arrMesh, 0);
for (int i = 0; i < meshDataTool.GetFaceCount(); i++)
{
// # Get the index in the vertex array.
var a = meshDataTool.GetFaceVertex(i, 0);
var b = meshDataTool.GetFaceVertex(i, 1);
var c = meshDataTool.GetFaceVertex(i, 2);
// # Get vertex position using vertex index.
var ap = meshDataTool.GetVertex(a);
var bp = meshDataTool.GetVertex(b);
var cp = meshDataTool.GetVertex(c);
// # Calculate face normal.
Vector3 nn = (bp - cp).Cross(ap - bp).Normalized();
// # Add face normal to current vertex normal.
// # This will not result in perfect normals, but it will be close.
meshDataTool.SetVertexNormal(a, nn + meshDataTool.GetVertexNormal(a));
meshDataTool.SetVertexNormal(b, nn + meshDataTool.GetVertexNormal(b));
meshDataTool.SetVertexNormal(c, nn + meshDataTool.GetVertexNormal(c));
}
for (int i = 0; i < meshDataTool.GetVertexCount(); i++)
{
var v = meshDataTool.GetVertexNormal(i).Normalized();
meshDataTool.SetVertexNormal(i,v);
}
arrMesh.ClearSurfaces();
meshDataTool.CommitToSurface(arrMesh);
return arrMesh;
}
private static int GetVoxelVertexIndex(Vector3I pos, int axis, Voxel[,,] voxels)
{
if (pos[0] < 0 || pos[1] < 0 || pos[2] < 0) return -1;
if (pos[0] >= voxels.GetLength(0) || pos[1] >= voxels.GetLength(1) || pos[2] >= voxels.GetLength(2)) return -1;
return voxels[pos[0], pos[1], pos[2]].vertexIndex;
}
private static Vector3I Offset(Vector3I pos, int axis, int delta)
{
Vector3I p = pos;
p[axis] += delta;
return p ;
}
private const float threshold = 0;
/**
* calculate the postion of each vertex. this sets up our 3 dimensional grid of voxels
* while also sampling each voxel
**/
static void CalculateVertexPositions(Vector3I size,List<Vector3> vertices, out Voxel[,,] voxels,Func<Vector3I,float> sampler,Func<Vector3I,bool> isCube)
{
voxels = new Voxel[size.X, size.Y, size.Z];
for (int x = 0; x < size.X - 1; x++)
{
for (int y = 0; y < size.Y - 1; y++)
{
for (int z = 0; z < size.Z - 1; z++)
{
// default values.
voxels[x, y, z].isOnSurface = false;
voxels[x, y, z].voxelEdgeMask = 0;
voxels[x, y, z].vertexIndex = -1;
voxels[x, y, z].cornerMask = 0;
int cornerMask = 0;
// sample the 8 corners for the voxel and create a corner mask
Sample[] samples = new Sample[8];
for (int i = 0; i < 8; i++)
{
var offset = voxelCornerOffsets[i];
var pos = new Vector3I(x + (int)offset.X, y + (int)offset.Y, z + (int)offset.Z);
var sample = sampler(new(pos.X, pos.Y, pos.Z));
samples[i].value = sample;
samples[i].point = pos;
cornerMask |= ((sample > threshold) ? (1 << i) : 0);
}
//Check for early termination if cell does not intersect boundary
if (cornerMask == 0 || cornerMask == 0xff)
{
continue;
}
// get edgemask from table using our corner mask
int edgeMask = edgeTable[cornerMask];
int edgeCrossings = 0;
var vertPos = Vector3.Zero;
for (int i = 0; i < 12; ++i)
{
//Use edge mask to check if it is crossed
if (!((edgeMask & (1 << i)) > 0))
{
continue;
}
//If it did, increment number of edge crossings
++edgeCrossings;
//Now find the point of intersection
int e0 = cubeEdges[i << 1];
int e1 = cubeEdges[(i << 1) + 1];
float g0 = samples[e0].value;
float g1 = samples[e1].value;
float t = (threshold - g0) / (g1 - g0);
vertPos += SJKMath.Lerp(samples[e0].point, samples[e1].point, t);
}
vertPos /= edgeCrossings;
vertPos = isCube(new Vector3I(x, y, z)) ? new Vector3(x, y, z) : vertPos;
voxels[x, y, z].vertexPosition = vertPos;
voxels[x, y, z].isOnSurface = true;
voxels[x, y, z].voxelEdgeMask = edgeMask;
voxels[x, y, z].vertexIndex = vertices.Count;
voxels[x, y, z].cornerMask = cornerMask;
vertices.Add(vertPos);
}
}
}
}
}

View File

@@ -0,0 +1 @@
uid://cmmco4k75cl77

View File

@@ -0,0 +1,409 @@
using System.Collections.Generic;
using Godot;
using System;
namespace SJK.Voxels;
public class SurfaceNetMesher<TVoxel>
{
private readonly IReadOnlyVoxelChunk<TVoxel> voxelChunk;
private readonly Func<Vector3I,TVoxel, (bool isCube,float sdf)> sampler;
private readonly float threshold;
private readonly EdgeMode edgeMode;
/*
* Give each voxel a position, an index, a corner mask, and an edge mask.
* isOnSurface basically asks if our mesh goes through this voxel
*/
private struct Voxel
{
public int vertexIndex;
public Vector3 vertexPosition;
public bool isOnSurface;
public int voxelEdgeMask;
public int cornerMask;
public bool isCube;
}
private struct Sample
{
public Vector3 point;
public float value;
}
private Voxel[,,] voxels;
private List<Vector3> vertices;
private List<Vector3> newVertices;
private List<int> triangles;
#region SurfaceNet Tables
// Define the edge table and cube edges with a set size
static private int[] edgeTable = new int[256];
static private int[] cubeEdges = new int[24];
static SurfaceNetMesher()
{
GenerateCubeEdgesTable();
GenerateIntersectionTable();
}
/*
* Set up the 8 corners of each voxel like this:
* Very important that this is set up the same way as the cube edges
*
* y z
* ^ /
* |
* 6----7
* /| /|
* 4----5 |
* | 2--|-3
* |/ |/
* 0----1 --> x
*
*/
private Vector3[] voxelCornerOffsets = new Vector3[8] {
new Vector3(0,0,0), // 0
new Vector3(1,0,0), // 1
new Vector3(0,1,0), // 2
new Vector3(1,1,0), // 3
new Vector3(0,0,1), // 4
new Vector3(1,0,1), // 5
new Vector3(0,1,1), // 6
new Vector3(1,1,1) // 7
};
/*
* Build an intersection table. This is a 2^(cube config) -> 2^(edge config) map
* There is only one entry for each possible cube configuration
* and the output is a 12-bit vector enumerating all edges
* crossing the 0-level
*/
private static void GenerateIntersectionTable()
{
for (int i = 0; i < 256; ++i)
{
int em = 0;
for (int j = 0; j < 24; j += 2)
{
var a = Convert.ToBoolean(i & (1 << cubeEdges[j]));
var b = Convert.ToBoolean(i & (1 << cubeEdges[j + 1]));
em |= a != b ? (1 << (j >> 1)) : 0;
}
edgeTable[i] = em;
}
}
/*
* Utility function to build a table of possible edges for a cube with each
* pair of points representing one edge i.e. [0,1,0,2,0,4,...] would be the
* edges from points 0 to 1, 0 to 2, and 0 to 4 respectively:
*
* y z
* ^ /
* |
* 6----7
* /| /|
* 4----5 |
* | 2--|-3
* |/ |/
* 0----1 --> x
*
*/
private static void GenerateCubeEdgesTable()
{
int k = 0;
for (int i = 0; i < 8; ++i)
{
for (int j = 1; j <= 4; j <<= 1)
{
int p = i ^ j;
if (i <= p)
{
cubeEdges[k++] = i;
cubeEdges[k++] = p;
}
}
}
}
#endregion
public SurfaceNetMesher(IReadOnlyVoxelChunk<TVoxel> voxelChunk, Func<Vector3I,TVoxel, (bool,float)> sampler, float threshold, EdgeMode edgeMode)
{
this.voxelChunk = voxelChunk;
this.sampler = sampler;
this.threshold = threshold;
this.edgeMode = edgeMode;
}
public Mesh CreateMesh()
{
vertices = new List<Vector3>();
triangles = new List<int>();
newVertices = new List<Vector3>();
CalculateVertexPositions();
// set the size of our buffer
var buffer = new int[4096];
// get the width, height, and depth of the sample space for our nested for loops
int width = voxelChunk.Bounds.Size.X + 1;
int height = voxelChunk.Bounds.Size.Y + 1;
int depth = voxelChunk.Bounds.Size.Z + 1;
int n = 0;
int[] pos = new int[3];
int[] R = new int[] { 1, width + 1, (width + 1) * (height + 1) };
float[] grid = new float[8];
int bufferNumber = 1;
// resize the buffer if it's not big enough
if (R[2] * 2 > buffer.Length)
buffer = new int[R[2] * 2];
for (pos[2] = 0; pos[2] < depth - 1; pos[2]++, n += width, bufferNumber ^= 1, R[2] = -R[2])
{
var bufferIndex = 1 + (width + 1) * (1 + bufferNumber * (height + 1));
for (pos[1] = 0; pos[1] < height - 1; pos[1]++, n++, bufferIndex += 2)
{
for (pos[0] = 0; pos[0] < width - 1; pos[0]++, n++, bufferIndex++)
{
// get the corner mask we calculated earlier
var mask = voxels[pos[0], pos[1], pos[2]].cornerMask;
// Early Termination Check
if (mask == 0 || mask == 0xff)
{
continue;
}
// get edge mask
var edgeMask = edgeTable[mask];
var vertex = new Vector3();
var edgeIndex = 0;
//For Every Cube Edge
for (var i = 0; i < 12; i++)
{
//Use Edge Mask to Check if Crossed
if (!Convert.ToBoolean(edgeMask & (1 << i)))
{
continue;
}
//If So, Increment Edge Crossing #
edgeIndex++;
//Find Intersection Point
var e0 = cubeEdges[i << 1];
var e1 = cubeEdges[(i << 1) + 1];
var g0 = grid[e0];
var g1 = grid[e1];
var t = g0 - g1;
if (System.Math.Abs(t) > 1e-16)
t = g0 / t;
else
continue;
//Interpolate Vertices, Add Intersections
if (!voxels[pos[0], pos[1], pos[2]].isCube)
for (int j = 0, k = 1; j < 3; j++, k <<= 1)
{
var a = e0 & k;
var b = e1 & k;
if (a != b)
vertex[j] += Convert.ToBoolean(a) ? 1f - t : t;
else
vertex[j] += Convert.ToBoolean(a) ? 1f : 0;
}
}
//Average Edge Intersections, Add to Coordinate
if (!voxels[pos[0], pos[1], pos[2]].isCube)
{
var s = 1f / edgeIndex;
for (var i = 0; i < 3; i++)
{
vertex[i] = pos[i] + s * vertex[i];
}
}else
vertex = voxels[pos[0], pos[1], pos[2]].vertexPosition;
//Add Vertex to Buffer, Store Pointer to Vertex Index
buffer[bufferIndex] = newVertices.Count;
newVertices.Add(vertex);
//Add Faces (Loop Over 3 Base Components)
for (var i = 0; i < 3; i++)
{
//First 3 Entries Indicate Crossings on Edge
if (!Convert.ToBoolean(edgeMask & (1 << i)))
{
continue;
}
//i - Axes, iu, iv - Ortho Axes
var iu = (i + 1) % 3;
var iv = (i + 2) % 3;
//Skip if on Boundary
if (pos[iu] == 0 || pos[iv] == 0)
continue;
//Otherwise, Look Up Adjacent Edges in Buffer
var du = R[iu];
var dv = R[iv];
//Flip Orientation Depending on Corner Sign
if (Convert.ToBoolean(mask & 1))
{
triangles.Add(buffer[bufferIndex]);
triangles.Add(buffer[bufferIndex - du - dv]);
triangles.Add(buffer[bufferIndex - du]);
triangles.Add(buffer[bufferIndex]);
triangles.Add(buffer[bufferIndex - dv]);
triangles.Add(buffer[bufferIndex - du - dv]);
}
else
{
triangles.Add(buffer[bufferIndex]);
triangles.Add(buffer[bufferIndex - du - dv]);
triangles.Add(buffer[bufferIndex - dv]);
triangles.Add(buffer[bufferIndex]);
triangles.Add(buffer[bufferIndex - du]);
triangles.Add(buffer[bufferIndex - du - dv]);
}
}
}
}
}
Godot.Collections.Array surfaceArray = [];
surfaceArray.Resize((int)Mesh.ArrayType.Max);
surfaceArray[(int)Mesh.ArrayType.Vertex] = vertices.ToArray();
// surfaceArray[(int)Mesh.ArrayType.TexUV] = uvs.ToArray();
surfaceArray[(int)Mesh.ArrayType.Normal] = new Vector3[vertices.Count];
surfaceArray[(int)Mesh.ArrayType.Index] = triangles.ToArray();
var arrMesh = new ArrayMesh();
// No blendshapes, lods, or compression used.
arrMesh.AddSurfaceFromArrays(Mesh.PrimitiveType.Triangles, surfaceArray);
MeshDataTool meshDataTool = new MeshDataTool();
meshDataTool.CreateFromSurface(arrMesh, 0);
for (int i = 0; i < meshDataTool.GetFaceCount(); i++)
{
// # Get the index in the vertex array.
var a = meshDataTool.GetFaceVertex(i, 0);
var b = meshDataTool.GetFaceVertex(i, 1);
var c = meshDataTool.GetFaceVertex(i, 2);
// # Get vertex position using vertex index.
var ap = meshDataTool.GetVertex(a);
var bp = meshDataTool.GetVertex(b);
var cp = meshDataTool.GetVertex(c);
// # Calculate face normal.
Vector3 nn = (bp - cp).Cross(ap - bp).Normalized();
// # Add face normal to current vertex normal.
// # This will not result in perfect normals, but it will be close.
meshDataTool.SetVertexNormal(a, nn + meshDataTool.GetVertexNormal(a));
meshDataTool.SetVertexNormal(b, nn + meshDataTool.GetVertexNormal(b));
meshDataTool.SetVertexNormal(c, nn + meshDataTool.GetVertexNormal(c));
}
for (int i = 0; i < meshDataTool.GetVertexCount(); i++)
{
var v = meshDataTool.GetVertexNormal(i).Normalized();
meshDataTool.SetVertexNormal(i,v);
}
arrMesh.ClearSurfaces();
meshDataTool.CommitToSurface(arrMesh);
return arrMesh;
}
/**
* calculate the postion of each vertex. this sets up our 3 dimensional grid of voxels
* while also sampling each voxel
**/
void CalculateVertexPositions()
{
voxels = new Voxel[voxelChunk.Bounds.Size.X, voxelChunk.Bounds.Size.Y, voxelChunk.Bounds.Size.Z];
for (int x = 0; x < voxelChunk.Bounds.Size.X - 1; x++)
{
for (int y = 0; y < voxelChunk.Bounds.Size.Y - 1; y++)
{
for (int z = 0; z < voxelChunk.Bounds.Size.Z - 1; z++)
{
// default values.
voxels[x, y, z].isOnSurface = false;
voxels[x, y, z].voxelEdgeMask = 0;
voxels[x, y, z].vertexIndex = -1;
voxels[x, y, z].cornerMask = 0;
voxels[x, y, z].isCube = false;
int cornerMask = 0;
// sample the 8 corners for the voxel and create a corner mask
Sample[] samples = new Sample[8];
for (int i = 0; i < 8; i++)
{
var offset = voxelCornerOffsets[i];
var pos = new Vector3I(x + (int)offset.X, y + (int)offset.Y, z + (int)offset.Z);
var sample = sampler(new(pos.X,pos.Y,pos.Z),voxelChunk.GetVoxel(pos));
voxels[x, y, z].isCube = voxels[x, y, z].isCube | sample.isCube;
samples[i].value = sample.sdf;
samples[i].point = pos;
cornerMask |= ((sample.sdf > threshold) ? (1 << i) : 0);
}
//Check for early termination if cell does not intersect boundary
if (cornerMask == 0 || cornerMask == 0xff)
{
continue;
}
// get edgemask from table using our corner mask
int edgeMask = edgeTable[cornerMask];
int edgeCrossings = 0;
var vertPos = Vector3.Zero;
for (int i = 0; i < 12; ++i)
{
//Use edge mask to check if it is crossed
if (!((edgeMask & (1 << i)) > 0))
{
continue;
}
//If it did, increment number of edge crossings
++edgeCrossings;
//Now find the point of intersection
int e0 = cubeEdges[i << 1];
int e1 = cubeEdges[(i << 1) + 1];
float g0 = samples[e0].value;
float g1 = samples[e1].value;
float t = (threshold - g0) / (g1 - g0);
vertPos += SJKMath.Lerp(samples[e0].point, samples[e1].point, voxels[x, y, z].isCube?.5f : t);
}
vertPos /= edgeCrossings;
voxels[x, y, z].vertexPosition = vertPos;
voxels[x, y, z].isOnSurface = true;
voxels[x, y, z].voxelEdgeMask = edgeMask;
voxels[x, y, z].vertexIndex = vertices.Count;
voxels[x, y, z].cornerMask = cornerMask;
vertices.Add(vertPos);
}
}
}
}
public enum EdgeMode : byte
{
NoEdge,//do not create voxels on edges of chunk
NegitiveEdges,//Create edges on - xyz axis only
PositveEdges,//Create edges on + xyz axis only
Outline,// Indcates taht the outer line of voxels are outside the chunk
}
}

View File

@@ -0,0 +1 @@
uid://bn37xdn3uyrdx

View File

@@ -0,0 +1,106 @@
using Godot;
using SJK.Voxels.Registry;
using System;
using System.Diagnostics;
namespace SJK.Voxels;
public sealed class VoxelChunk<TVoxel,TChunk> : IVoxelChunk<TVoxel,TChunk>, IReadOnlyVoxelChunk<TVoxel,TChunk> where TChunk : IChunkSize<TChunk>
{
// public Vector3I Size => new(TChunk.Size, TChunk.Size, TChunk.Size);
public VoxelBounds Bounds => new(Vector3I.Zero,new(TChunk.Size,TChunk.Size,TChunk.Size));
// TVoxel[,,] voxels;
TVoxel[] voxels;
public VoxelChunk()
{
voxels = new TVoxel[TChunk.Size*TChunk.Size*TChunk.Size];
// voxels = new TVoxel[Size.X, Size.Y, Size.Z];
}
public VoxelChunk(TVoxel[] voxels)
{
// if (voxels.GetLength(0) != TChunk.Size && voxels.GetLength(1) != TChunk.Size && voxels.GetLength(2) != TChunk.Size)
// {
// throw new Exception($"{nameof(voxels)} must be of size {Size}. Supplied array was of length ({voxels.GetLength(0)}, {voxels.GetLength(1)}, {voxels.GetLength(0)})");
// }
this.voxels = voxels;
}
public VoxelChunk(TVoxel fill) : this()
{
Array.Fill(voxels,fill);
}
public TVoxel GetVoxel(VoxelPos position)
{
Debug.Assert(IsInBounds(position),$" Position {position} is out of bounds, Size:{TChunk.Size} plese check {nameof(IsInBounds)} before Calling");
return voxels[new VoxelPos<TChunk>(position.X, position.Y, position.Z).Index];
}
public bool IsInBounds(VoxelPos position) => IChunkSize<TChunk>.IsInBounds((Vector3I)position);
public void SetVoxel(VoxelPos position, TVoxel voxel)
{
Debug.Assert(IsInBounds(position),$" Position {position} is out of bounds, Size:{TChunk.Size} plese check {nameof(IsInBounds)} before Calling");
voxels[new VoxelPos<TChunk>(position.X, position.Y, position.Z).Index] = voxel;
}
public TVoxel[] GetRawVoxelData() => voxels;
public TVoxel GetVoxel(VoxelPos<TChunk> position) => voxels[position.Index];
public void SetVoxel(VoxelPos<TChunk> position, TVoxel voxel) => voxels[position.Index] = voxel;
public void Fill(TVoxel voxel)=> Array.Fill(voxels,voxel);
}
public class PaletteVoxelChunk<TVoxel, TChunkSize> : IVoxelChunk<TVoxel, TChunkSize> where TVoxel : IEquatable<TVoxel> where TChunkSize : IChunkSize<TChunkSize>
{
private VoxelPalette<VoxelHandle<PaletteLevel>, PaletteLevel, TVoxel> voxelPalette;
private readonly IVoxelChunk<VoxelHandle<PaletteLevel>,TChunkSize> innerStore;
public VoxelBounds Bounds => new(Vector3I.Zero,new(TChunkSize.Size,TChunkSize.Size,TChunkSize.Size));
public PaletteVoxelChunk()
{
innerStore = new VoxelChunk<VoxelHandle<PaletteLevel>,TChunkSize>();
}
public PaletteVoxelChunk(IVoxelChunk<VoxelHandle<PaletteLevel>,TChunkSize> innerStore)
{
this.innerStore = innerStore;
}
public VoxelChunkEnumerator<TVoxel> GetEnumerator() => new VoxelChunkEnumerator<TVoxel>(this);
public TVoxel GetVoxel(VoxelPos<TChunkSize> position)
{
var handle = innerStore.GetVoxel(position);
return voxelPalette.Get(handle);
}
public TVoxel GetVoxel(VoxelPos position) => GetVoxel(new VoxelPos<TChunkSize>(position.X, position.Y, position.Z));
public bool IsInBounds(VoxelPos position) =>
position.X >= 0 && position.X < TChunkSize.Size
&& position.Y >= 0 && position.Y < TChunkSize.Size
&& position.Z >= 0 && position.Z < TChunkSize.Size;
public void SetVoxel(VoxelPos<TChunkSize> position, TVoxel voxel)
{
innerStore.SetVoxel(position, voxelPalette.GetOrAddEntry(voxel));
}
public void SetVoxel(VoxelPos position, TVoxel voxel) => SetVoxel(new VoxelPos<TChunkSize>(position.X, position.Y, position.Z), voxel);
VoxelChunkEnumerator<TVoxel, TChunkSize> IReadOnlyVoxelChunk<TVoxel, TChunkSize>.GetEnumerator()
{
return new VoxelChunkEnumerator<TVoxel, TChunkSize>(this);
}
public void Fill(TVoxel voxel)
{
voxelPalette.Clear();
innerStore.Fill(voxelPalette.GetOrAddEntry(voxel));
}
}

View File

@@ -0,0 +1 @@
uid://dsvwlpnv2gyrv

View File

@@ -0,0 +1,99 @@
using Godot;
namespace SJK.Voxels;
//TODO need to have an version for not bound to TChunkSize, and VoxelChunkEnurmatero for <TVoxel,TSize>, and likly be a good idea for fversions that change directions
public struct VoxelChunkEnumerator<TVoxel>
{
private readonly IReadOnlyVoxelChunk<TVoxel> chunk;
// private readonly Vector3I size;
private int x, y, z;
public VoxelChunkEnumerator(IReadOnlyVoxelChunk<TVoxel> chunk)
{
this.chunk = chunk;
// this.size = chunk.Size;
this.x = chunk.Bounds.Min.X-1;
this.y = chunk.Bounds.Min.Y;
this.z = chunk.Bounds.Min.Z;
Current = default;
}
public (Vector3I Position, TVoxel Voxel) Current { get; private set; }
public bool MoveNext()
{
x++;
if (x >= chunk.Bounds.Max.X)
{
x = chunk.Bounds.Min.X;
y++;
if (y >= chunk.Bounds.Max.Y)
{
y = chunk.Bounds.Min.Y;
z++;
if (z >= chunk.Bounds.Max.Z)
return false;
}
}
var pos = new Vector3I(x, y, z);
Current = (pos, chunk.GetVoxel(pos));
return true;
}
}
public struct VoxelChunkEnumerator<TVoxel,TChunkSize> where TChunkSize : IChunkSize<TChunkSize>
{
private readonly IReadOnlyVoxelChunk<TVoxel,TChunkSize> chunk;
// private readonly Vector3I size;
private uint pos = 0;
public VoxelChunkEnumerator(IReadOnlyVoxelChunk<TVoxel,TChunkSize> chunk)
{
this.chunk = chunk;
// this.size = chunk.Size;
Current = default;
}
public (VoxelPos<TChunkSize> Position, TVoxel Voxel) Current { get; private set; }
public bool MoveNext()
{
if (pos >= IChunkSize<TChunkSize>.SizeCubed)
{
return false;
}
pos++;
// var pos = new Vector3I(x, y, z);
var adjustedPos = new VoxelPos<TChunkSize>(pos-1);
Current = (adjustedPos, chunk.GetVoxel(adjustedPos));
return true;
}
}
public struct VoxelPositionChunkEnumerator<TVoxel,TChunkSize> where TChunkSize : IChunkSize<TChunkSize>
{
private readonly IReadOnlyVoxelChunk<TVoxel,TChunkSize> chunk;
// private readonly Vector3I size;
private uint pos = 0;
public VoxelPositionChunkEnumerator(IReadOnlyVoxelChunk<TVoxel,TChunkSize> chunk)
{
this.chunk = chunk;
// this.size = chunk.Size;
Current = default;
}
public VoxelPos<TChunkSize> Current { get; private set; }
public bool MoveNext()
{
if (pos >= IChunkSize<TChunkSize>.SizeCubed)
{
return false;
}
pos++;
// var pos = new Vector3I(x, y, z);
var adjustedPos = new VoxelPos<TChunkSize>(pos-1);
// Current = (adjustedPos, chunk.GetVoxel(adjustedPos));
return true;
}
}

View File

@@ -0,0 +1 @@
uid://cd5ybj683y0be

View File

@@ -0,0 +1,202 @@
using Godot;
using System;
using System.Diagnostics;
namespace SJK.Voxels;
public sealed class VoxelSlice<TVoxel> : IVoxelChunk<TVoxel>, IReadOnlyVoxelChunk<TVoxel>
{
private readonly IVoxelChunk<TVoxel> voxelChunk;
public VoxelSlice(IVoxelChunk<TVoxel> voxelChunk, Vector3I offset, Vector3I size)
{
if (voxelChunk is VoxelSlice<TVoxel> slice)
{
voxelChunk = slice.voxelChunk;
offset = offset + slice.Offset;
}
#if DEBUG
if (offset.X < voxelChunk.Bounds.Min.X || offset.Y < voxelChunk.Bounds.Min.Y || offset.Z < voxelChunk.Bounds.Min.Z)
{
throw new IndexOutOfRangeException($"{nameof(offset)} has an value less then Zero : {offset}");
}
if (offset.X + size.X > voxelChunk.Bounds.Max.X || offset.Y + size.Y > voxelChunk.Bounds.Max.Y || offset.Z + size.Z > voxelChunk.Bounds.Max.Z)
{
throw new IndexOutOfRangeException($"{nameof(offset)} has an value greater then the size of the {nameof(voxelChunk)} : {offset + size},Size : {voxelChunk.Bounds}");
}
#endif
this.voxelChunk = voxelChunk;
Offset = offset;
Size = size;
}
public Vector3I Offset { get; }
public Vector3I Size { get; }
public VoxelBounds Bounds => throw new NotImplementedException();
public TVoxel GetVoxel(VoxelPos position) => voxelChunk.GetVoxel(position + Offset);
// public bool IsInBounds(VoxelPos position) => voxelChunk.IsInBounds(position + Offset);
public void SetVoxel(VoxelPos position, TVoxel voxel) => voxelChunk.SetVoxel(position + Offset, voxel);
public VoxelChunkEnumerator<TVoxel> GetEnumerator()
=> new VoxelChunkEnumerator<TVoxel>(this);
}
public class ReadOnlyVirtualChunk<TVoxel> : IReadOnlyVoxelChunk<TVoxel>
{
private readonly Func<VoxelPos, TVoxel> func;
public ReadOnlyVirtualChunk(Vector3I size, Func<VoxelPos, TVoxel> func)
{
Size = size;
this.func = func;
}
public Vector3I Size { get; }
public VoxelBounds Bounds => new VoxelBounds(Vector3I.Zero,Size);
public VoxelChunkEnumerator<TVoxel> GetEnumerator() => new VoxelChunkEnumerator<TVoxel>(this);
public TVoxel GetVoxel(VoxelPos position) => func(position);
public bool IsInBounds(VoxelPos position)=>
position.X >= 0 && position.X < Size.X
&& position.Y >= 0 && position.Y < Size.Y
&& position.Z >= 0 && position.Z < Size.Z ;
}
public class NeighborAwareChunk<TVoxel,TChunkSize> : IReadOnlyVoxelChunk<TVoxel,TChunkSize> where TChunkSize : IChunkSize<TChunkSize>
{
private readonly ChunkPos<TChunkSize> chunkPos;
private readonly IReadOnlyVoxelChunk<TVoxel,TChunkSize> centerChunk;
private readonly IReadOnlyVoxelChunk<TVoxel,TChunkSize>?[] neigbors;
private readonly Func<VoxelPos,TVoxel> getWorld;
private readonly TVoxel _default;
public NeighborAwareChunk(ChunkPos<TChunkSize> chunkPos,IReadOnlyVoxelChunk<TVoxel,TChunkSize> centerChunk, IReadOnlyVoxelChunk<TVoxel,TChunkSize>?[] neigbors, Func<VoxelPos,TVoxel> getWorld, TVoxel @default = default)
{
this.chunkPos = chunkPos;
this.centerChunk = centerChunk;
this.neigbors = neigbors;
this.getWorld = getWorld;
this._default = @default;
}
public VoxelBounds Bounds => centerChunk.Bounds;
public VoxelChunkEnumerator<TVoxel> GetEnumerator() => new VoxelChunkEnumerator<TVoxel>(this);
public bool IsInBounds(VoxelPos position) =>
position.X >= -1 && position.X < TChunkSize.Size + 1
&& position.Y >= -1 && position.Y < TChunkSize.Size + 1
&& position.Z >= -1 && position.Z < TChunkSize.Size + 1;
public TVoxel GetVoxel(VoxelPos pos)
{
// shift into neighbor-aware index space
int x = pos.X;
int y = pos.Y;
int z = pos.Z;
int sizeX = TChunkSize.Size;
int sizeY = TChunkSize.Size;
int sizeZ = TChunkSize.Size;
// Check if within center chunk
if (x >= 0 && x < sizeX &&
y >= 0 && y < sizeY &&
z >= 0 && z < sizeZ)
{
return centerChunk.GetVoxel(pos);
}
// Encode offsets (-1,0,+1) into a small index
int dx = (x < 0) ? -1 : (x >= sizeX ? 1 : 0);
int dy = (y < 0) ? -1 : (y >= sizeY ? 1 : 0);
int dz = (z < 0) ? -1 : (z >= sizeZ ? 1 : 0);
int code = ((dx + 1) * 9) + ((dy + 1) * 3) + (dz + 1);
// That gives you a unique 0..26 index for neighbors
// GD.PrintS(x, y, z, dx, dy, dz, code, new Vector3I(sizeX - 1, y, z));
if (neigbors is null)
{
return HandleEdgeOrCorner(dx, dy, dz, x, y, z);
}
try
{
switch (code)
{
case 13: // (0,0,0) center, already handled
return _default;
case 12: // (-1,0,0) left
return neigbors[(int)Direction.Forward] is not null ? neigbors[(int)Direction.Forward].GetVoxel(new Vector3I(x, y, z + sizeZ)) : _default;
case 14: // (1,0,0) right
return neigbors[(int)Direction.BackWord] is not null ? neigbors[(int)Direction.BackWord].GetVoxel(new Vector3I(x, y, z - sizeZ)) : _default;
case 10: // (0,-1,0) down
return neigbors[(int)Direction.Down] is not null ? neigbors[(int)Direction.Down].GetVoxel(new Vector3I(x, y + sizeY, z)) : _default;
case 16: // (0,1,0) up
return neigbors[(int)Direction.Up] is not null ? neigbors[(int)Direction.Up].GetVoxel(new Vector3I(x, y - sizeY, z)) : _default;
case 4: // (0,0,-1) back
return neigbors[(int)Direction.Left] is not null ? neigbors[(int)Direction.Left].GetVoxel(new Vector3I(x + sizeX, y, z)) : _default;
case 22: // (0,0,1) front
return neigbors[(int)Direction.Right] is not null ? neigbors[(int)Direction.Right].GetVoxel(new Vector3I(x - sizeX, y, z)) : _default;
// Edges and corners:
default:
return HandleEdgeOrCorner(dx, dy, dz, x, y, z);
}
}
catch (System.Exception)
{
GD.PrintS(code, x, y, z);
throw;
}
}
private TVoxel HandleEdgeOrCorner(int dx, int dy, int dz, int x, int y, int z)
{
// Example: if two offsets non-zero, its edge; three = corner.
// int nonZero = (dx != 0 ? 1 : 0) + (dy != 0 ? 1 : 0) + (dz != 0 ? 1 : 0);
// if (nonZero == 2)
// {
// // Edge neighbor: need 2 chunks combined (you can decide to require them both or fallback)
// // Example for (-1,-1,0):
// if (dx == -1 && dy == -1)
// return Left?.GetVoxel(new Vector3I(x + Size.X, y, z))
// ?? Down?.GetVoxel(new Vector3I(x, y + Size.Y, z))
// ?? default;
// }
// else if (nonZero == 3)
// {
// // Corner
// return default; // or pick fallback value
// }
return getWorld(new Vector3I(x, y, z) + (chunkPos * new Vector3I(TChunkSize.Size,TChunkSize.Size,TChunkSize.Size)));
return _default;
}
public TVoxel GetVoxel(VoxelPos<TChunkSize> position) => GetVoxel(new Vector3I(position.X, position.Y, position.Z));
VoxelChunkEnumerator<TVoxel, TChunkSize> IReadOnlyVoxelChunk<TVoxel, TChunkSize>.GetEnumerator()
{
return new VoxelChunkEnumerator<TVoxel, TChunkSize>(this);
}
}
public enum Direction : byte
{
Forward,
Right,
BackWord,
Left,
Up,
Down,
}

View File

@@ -0,0 +1 @@
uid://b2rcodqw5v2ku

View File

@@ -0,0 +1,136 @@
namespace ChickenGameTest;
using System;
using System.Collections.Generic;
using System.Numerics;
// using System.Management;
// public partial class StructuralInstance<TAttribute> where TAttribute : class
// {
public sealed class StructuralBuilder<TAttribute> where TAttribute : class
{
private readonly Dictionary<int, TAttribute> _components = [];
public StructuralBuilder() { }
public StructuralBuilder(IStructuralInstance<TAttribute> existing)
{
foreach (var (id, value) in existing.GetAttributes())
{
_components[id] = value;
}
// for (int i = 0; i < existing.ComponentCount; i++)
// {
// var typeId = existing._attributesTypeIds[i];
// var component = existing.GetComponentAt(i);
// _components[typeId] = component;
// }
}
public StructuralBuilder<TAttribute> Add<T>(T component) where T : TAttribute
{
int id = ComponentTypeRegistry.GetId<T>();
_components[id] = component;
return this;
}
public StructuralBuilder<TAttribute> Upsert<T>(Func<T, T> ifExists, Func<T> none) where T : TAttribute
{
int id = ComponentTypeRegistry.GetId<T>();
_components[id] = _components.TryGetValue(id, out var old) ? ifExists((T)old) : none();
return this;
}
public StructuralBuilder<TAttribute> CombineWith(IStructuralInstance<TAttribute> other, Action<CombineBinder>? configure = null)
{
var binder = new CombineBinder();
configure?.Invoke(binder);
foreach (var (id, value) in other.GetAttributes())
{
if (binder._ignores.Contains(id))
{
continue;
}
if (_components.TryGetValue(id, out var existing)
&& binder._rules.TryGetValue(id, out var rule))
{
_components[id] = rule.Invoke(existing, value);
continue;
}
_components[id] = value;
}
return this;
}
public StructuralBuilder<TAttribute> Remove<T>() where T : TAttribute
{
int id = ComponentTypeRegistry.GetId<T>();
_components.Remove(id);
return this;
}
public bool Has<T>() where T : TAttribute
{
int id = ComponentTypeRegistry.GetId<T>();
return _components.ContainsKey(id);
}
public StructuralInstance<TAttribute> Build()
{
int count = _components.Count;
var typeIds = new int[count];
var components = new TAttribute[count];
int i = 0;
foreach (var kv in _components)
{
typeIds[i] = kv.Key;
components[i] = kv.Value;
i++;
}
Array.Sort(typeIds, components);
return new StructuralInstance<TAttribute>(typeIds, components);
}
public MutableStructuralInstance<TAttribute> BuildMutable()
{
// int count = _components.Count;
var sortedList = new SortedList<int, TAttribute>(_components);
// var typeIds = new int[count];
// var components = new TAttribute[count];
// int i = 0;
// foreach (var kv in _components)
// {
// typeIds[i] = kv.Key;
// components[i] = kv.Value;
// i++;
// }
// Array.Sort(typeIds, components);
return new MutableStructuralInstance<TAttribute>(sortedList);
}
public class CombineBinder
{
internal readonly Dictionary<int, Func<TAttribute, TAttribute, TAttribute>> _rules = [];
internal readonly HashSet<int> _ignores = [];
public CombineBinder Bind<T>(Func<T, T, T> func) where T : TAttribute
{
_rules[ComponentType<T>.Id] = (a, b) => func((T)a, (T)b);
return this;
}
public CombineBinder Ignore<T>() where T : TAttribute
{
_ignores.Add(ComponentType<T>.Id);
return this;
}
}
}
// }

View File

@@ -0,0 +1 @@
uid://bk721rnhegl0x

View File

@@ -0,0 +1,258 @@
namespace ChickenGameTest;
using System;
using System.Collections.Generic;
using System.Linq;
// using Chickensoft.Sync.Primitives;
using SJK.Functional;
public interface IStructuralInstance<TAttribute> : IEquatable<IStructuralInstance<TAttribute>>
{
int ComponentCount { get; }
bool Has<T>() where T : TAttribute;
Option<T> Get<T>() where T : TAttribute;
// TAttribute GetComponentAt(int index);
IEnumerable<(int Id, TAttribute Value)> GetAttributes();
bool IEquatable<IStructuralInstance<TAttribute>>.Equals(IStructuralInstance<TAttribute>? other) => Enumerable.SequenceEqual(GetAttributes(), other.GetAttributes(), EqualityComparer<(int, TAttribute)>.Default);
int ComputeHashCode()
{
HashCode hash = new HashCode();
hash.Add(ComponentCount);
foreach (var item in GetAttributes())
{
hash.Add(item.Id);
hash.Add(item.Value);
}
return hash.ToHashCode();
}
}
public sealed partial class StructuralInstance<TAttribute> : IStructuralInstance<TAttribute>, IEquatable<StructuralInstance<TAttribute>> where TAttribute : class
{
private readonly int _hashCode;
internal readonly int[] _attributesTypeIds;
internal readonly TAttribute[] _attributes;
public int ComponentCount => _attributes.Length;
public TAttribute GetComponentAt(int index) => _attributes[index];
internal StructuralInstance(int[] componentTypeIds, TAttribute[] attributes)
{
_attributesTypeIds = componentTypeIds;
_attributes = attributes;
_hashCode = (this as IStructuralInstance<TAttribute>).ComputeHashCode();
}
public bool Has<T>() where T : TAttribute
{
// int typeId = ComponentTypeRegistry.GetId<T>();
var typeId = ComponentType<T>.Id;
return Array.BinarySearch(_attributesTypeIds, typeId) >= 0;
}
public Option<T> Get<T>() where T : TAttribute
{
// int typeId = ComponentTypeRegistry.GetId<T>();
var typeId = ComponentType<T>.Id;
int index = Array.BinarySearch(_attributesTypeIds, typeId);
if (index < 0)
{
return Option<T>.None;
}
return Option<T>.Some((T)_attributes[index]);
}
public bool Equals(StructuralInstance<TAttribute>? other)
{
if (ReferenceEquals(this, other))
{
return true;
}
if (other is null)
{
return false;
}
return _hashCode == other._hashCode;// && Enumerable.SequenceEqual(_attributes, other._attributes, EqualityComparer<TAttribute>.Default);// && AttributesAreEqual(other);
}
public override bool Equals(object? obj) => obj is StructuralInstance<TAttribute> value? Equals(value) : obj is IStructuralInstance<TAttribute> v && v.Equals(this);
public override int GetHashCode() => _hashCode;
public IEnumerable<(int Id, TAttribute Value)> GetAttributes()
{
for (int i = 0; i < ComponentCount; i++)
{
yield return (_attributesTypeIds[i], _attributes[i]);
}
}
public override string ToString() => base.ToString();
}
public sealed class MutableStructuralInstance<TAttribute> : IStructuralInstance<TAttribute>, IEquatable<MutableStructuralInstance<TAttribute>> where TAttribute : class
{
private readonly SortedList<int, TAttribute> _attributes = [];
public int ComponentCount => _attributes.Count;
public MutableStructuralInstance(SortedList<int, TAttribute> attributes)
{
_attributes = attributes;
}
public Option<T> Get<T>() where T : TAttribute => _attributes.TryGetValue(ComponentType<T>.Id, out var attribute) ? Option<T>.Some((T)attribute) : Option<T>.None;
public IEnumerable<(int Id, TAttribute Value)> GetAttributes() => _attributes.Select(x => (x.Key, x.Value));
public TAttribute GetComponentAt(int index) => _attributes[index];
public bool Has<T>() where T : TAttribute => _attributes.ContainsKey(ComponentType<T>.Id);
public void Set<T>(T value) where T : TAttribute => _attributes[ComponentType<T>.Id] = value;
public override int GetHashCode() => (this as IStructuralInstance<TAttribute>).ComputeHashCode();
public bool Equals(MutableStructuralInstance<TAttribute>? other) => other is not null && Enumerable.SequenceEqual(_attributes, other._attributes, EqualityComparer<KeyValuePair<int, TAttribute>>.Default);
public override bool Equals(object? obj) => obj is MutableStructuralInstance<TAttribute> other ? Equals(other) : obj is IStructuralInstance<TAttribute> v && v.Equals(this);
}
public static class ComponentTypeRegistry
{
private static readonly Dictionary<Type, int> _typeToId = [];
private static readonly List<Type> _idToType = [];
public static int GetId(Type type)
{
if (_typeToId.TryGetValue(type, out var id))
{
return id;
}
id = _idToType.Count;
_typeToId[type] = id;
_idToType.Add(type);
return id;
}
public static int GetId<T>() => GetId(typeof(T));
}
public static class ComponentType<T>
{
public static readonly int Id = ComponentTypeRegistry.GetId<T>();
public static readonly bool CacheTransitions = Resolve();
private static bool Resolve()
{
var attr = typeof(T).GetCustomAttributes(typeof(ComponentOptionsAttribute), false);
return attr.OfType<ComponentOptionsAttribute>().FirstOrDefault().ToOption().Map(static f => f.CacheTransitions).Or(true);
}
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
public sealed class ComponentOptionsAttribute : Attribute
{
public bool CacheTransitions { get; init; } = true;
}
public interface IStructureRegistry<TAttribute> where TAttribute : class
{
StructuralInstance<TAttribute> Canonicalize(StructuralInstance<TAttribute> value);
StructuralInstance<TAttribute> AddOrReplaceAttribute<T>(StructuralInstance<TAttribute> instance, T attribute) where T : TAttribute;
StructuralInstance<TAttribute> RemoveAttribute<T>(StructuralInstance<TAttribute> instance) where T : TAttribute;
}
public class StructuralInstanceManger<TAttribute> : IStructureRegistry<TAttribute> where TAttribute : class
{
private readonly HashSet<StructuralInstance<TAttribute>> _instances = [];
public StructuralInstance<TAttribute> Add<T>(StructuralInstance<TAttribute> instance, T component) where T : TAttribute
{
var builder = new StructuralBuilder<TAttribute>(instance).Add(component);
return Canonicalize(builder.Build());
}
public StructuralInstance<TAttribute> Canonicalize(StructuralInstance<TAttribute> value)
{
if (_instances.TryGetValue(value, out var id))
{
return id;
}
_instances.Add(value);
return value;
}
public StructuralInstance<TAttribute> AddOrReplaceAttribute<T>(StructuralInstance<TAttribute> instance, T attribute) where T : TAttribute
{
if (!graph.TryGetValue(instance, out var results))
{
graph[instance] = results = [];
}
for (int i = 0; i < results.Count; i++)
{
if (results[i] is AddTransitionEntry<T> addTransitionEntry && EqualityComparer<T>.Default.Equals(addTransitionEntry.Value, attribute))
{
return addTransitionEntry.Result;
}
}
var result =new AddTransitionEntry<T>(ComponentType<T>.Id, attribute, Canonicalize(new StructuralBuilder<TAttribute>(instance).Add(attribute).Build()));
if (ComponentType<T>.CacheTransitions)
{
results.Add(result);
}
return result.Result;
}
public StructuralInstance<TAttribute> RemoveAttribute<T>(StructuralInstance<TAttribute> instance) where T : TAttribute
{
if (!graph.TryGetValue(instance, out var results))
{
graph[instance] = results = [];
}
var id = ComponentType<T>.Id;
for (int i = 0; i < results.Count; i++)
{
if (results[i] is RemoveTransitionEntry removeTransitionEntry && removeTransitionEntry.Id == id)
{
return removeTransitionEntry.Result;
}
}
var result =new RemoveTransitionEntry(id, Canonicalize(new StructuralBuilder<TAttribute>(instance).Remove<T>().Build()));
if (ComponentType<T>.CacheTransitions)
{
results.Add(result);
}
return result.Result;
}
private Dictionary<StructuralInstance<TAttribute>, List<TransitionEntry>> graph = [];
private record TransitionEntry(int Id);
private record AddTransitionEntry<T>(int Id, T Value, StructuralInstance<TAttribute> Result) : TransitionEntry(Id) where T : TAttribute;
private record RemoveTransitionEntry(int Id, StructuralInstance<TAttribute> Result) : TransitionEntry(Id);
// private record ModifyTransitionEntry<T>(int Id, TAttribute Value, StructuralInstance<TAttribute> Result) : TransitionEntry(Id) where T : TAttribute;
}
public class AutoStructureInstance<TAttribute> where TAttribute : class
{
private StructuralInstance<TAttribute> _structuralInstance;
private IStructureRegistry<TAttribute> _registry;
private Dictionary<int,List<Delegate>> _bindings = [];
public void Set<T>(T? value) where T : TAttribute
{
var old = _structuralInstance.Get<T>();
if (old.HasValue && old.Value.Equals(value))
{
return;
}
if (value is null)
{
_structuralInstance = _registry.RemoveAttribute<T>(_structuralInstance);
}
else
{
_structuralInstance = _registry.AddOrReplaceAttribute(_structuralInstance, value);
}
_bindings[ComponentType<T>.Id].ForEach(item => item.DynamicInvoke(old,value));
}
public void Bind<T>(Action<T?, T?> callback) where T : TAttribute
{
int id = ComponentType<T>.Id;
if (!_bindings.TryGetValue(id, out var list))
{
_bindings[id] = list = [];
}
list.Add(callback);
}
}

View File

@@ -0,0 +1 @@
uid://cexmjk01dgtbe

View File

@@ -0,0 +1,66 @@
namespace ChickenGameTest;
using System.Diagnostics;
using Godot;
using SJK.Functional;
public class TestStructural
{
public static void Test()
{
var a = new StructuralBuilder<object>()
.Add(new Vector3(0,0,0))
.Add(new Vector2(0,5))
.Add(new Vector4(0,0,0,0))
.Add(new Aabb())
.Add(new int())
.Add(new float())
.Add(new Node())
.Add(new Node())
.Add(new Node2D())
.Add("")
.Add(new test(){a = 5, b = "gg"})
.Build();
var registy = new StructuralInstanceManger<object>();
var b = registy.Add(a,new Vector2(1,2));
var c = registy.Add(a,new Vector2(1,2));
var d = registy.Canonicalize(new StructuralBuilder<object>(a).Add(new Vector2(1,2)).Build());
var g = new StructuralBuilder<object>(a).Add(Option<int>.Some(5)).Build();
var h = new StructuralBuilder<object>(a).Add(Option<int>.Some(5)).BuildMutable();
GD.Print(g.Equals(h));
GD.Print(ReferenceEquals(b,c));
GD.Print(ReferenceEquals(b,d));
GD.Print(ComponentType<Vector3>.Id);
GD.Print(ComponentType<Vector2>.Id);
var help = new StructuralBuilder<object>(a)
.CombineWith(b, static configure => configure
.Bind<Vector2>(static (a, b) => a + b)
.Ignore<Vector4>())
.Add(Vector2I.Zero)
.Build();
GD.Print(help.Get<Vector2>().Map(i=>$"{i}").OrDefault("none"));
var timer = new Stopwatch();
timer.Start();
for (int i = 0; i < 100000; i++)
{
var e = registy.AddOrReplaceAttribute(a,new test());
}
timer.Stop();
GD.Print(timer.Elapsed);
timer.Reset();
timer.Start();
for (int i = 0; i < 100000; i++)
{
var e = registy.Canonicalize(new StructuralBuilder<object>(a).Add(new test()).Build());
}
timer.Stop();
GD.Print(timer.Elapsed);
}
}
[ComponentOptions(CacheTransitions = true)]
record test
{
public int a;
public string b;
}

View File

@@ -0,0 +1 @@
uid://br6rfqtryq0cx

View File

@@ -0,0 +1,21 @@
using System.Numerics;
namespace SJK.Voxels.Registry;
public interface IBitBacking<TAttribute, TBitBacking>
where TAttribute : class
where TBitBacking : unmanaged, IBinaryInteger<TBitBacking> {
static abstract AttributeDescriptor<TAttribute, TBitBacking> AttributeBacking();
static abstract int BitLength { get; }
TBitBacking GetBits();
}
public interface IBitBacking<T, TAttribute, TBitBacking> : IBitBacking<TAttribute, TBitBacking>
where T : TAttribute, IBitBacking<T, TAttribute, TBitBacking>
where TAttribute : class
where TBitBacking : unmanaged, IBinaryInteger<TBitBacking>
{
static abstract T Create(TBitBacking bits);
static abstract TBitBacking GetBits(T value);
}

View File

@@ -0,0 +1 @@
uid://bemjbjlx8y6q6

View File

@@ -0,0 +1,501 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Godot;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using SJK.Functional;
namespace SJK.Voxels.Registry;
public enum AttributeBacking
{
Auto, // try bits first, fall back to object if no free bits
Bits, // force bit packing
Object // force object storage
}
public sealed record AttributeDescriptor<TAttribute,TBitBacking>(
AttributeBacking Backing,
int BitOffset ,
int BitLength
) where TBitBacking : unmanaged , IBinaryInteger<TBitBacking> ;
public interface IVoxelDefinition<TAttrribute> where TAttrribute : class
{
string Name { get; }
VoxelInstanceBuilder<TAttrribute> CreateInstance();
IVoxelInstance<TAttrribute> CreateDefaultInstance();
T GetAttribute<T>(Func<T> ctor) where T : TAttrribute;
Option<T> GetAttribute<T>() where T : TAttrribute;
}
public class VoxelDefinition<TAttrribute> : IVoxelDefinition<TAttrribute>, IEquatable<VoxelDefinition<TAttrribute>> where TAttrribute : class
{
public VoxelDefinition(string name, IReadOnlyDictionary<Type, TAttrribute> attributes)
{
Name = name;
Attributes = attributes;
HashCode = ComputeHashCode();
}
public readonly int HashCode;
public string Name { get; }
public IReadOnlyDictionary<Type, TAttrribute> Attributes { get; }
public T GetAttribute<T>(Func<T> ctor) where T : TAttrribute
{
return Attributes.TryGetValue(typeof(T), out var v) ? (T)v : ctor();
}
public Option<T> GetAttribute<T>() where T : TAttrribute
{
return Attributes.TryGetValue(typeof(T), out var v) ? Option<T>.Some((T)v) : Option<T>.None;
}
public static VoxelDefinition<TAttrribute> Create(Action<VoxelDefinitionBuilder<TAttrribute>> config)
{
var builder = new VoxelDefinitionBuilder<TAttrribute>();
config(builder);
return builder.Build();
}
public VoxelInstanceBuilder<TAttrribute> CreateInstance()
{
return new VoxelInstanceBuilder<TAttrribute>(this);
}
public bool Equals(VoxelDefinition<TAttrribute> other)
{
if (ReferenceEquals(this, other))
return true;
if (HashCode != other.HashCode)
return false;
var valueCompare = EqualityComparer<TAttrribute>.Default;
return Name == other.Name && Attributes.Count == other.Attributes.Count && Attributes.Keys.All(key => other.Attributes.ContainsKey(key) && valueCompare.Equals(Attributes[key], other.Attributes[key]));
}
private int ComputeHashCode()
{
HashCode hash = new HashCode();
hash.Add(Name.GetHashCode());
foreach (var item in Attributes.Values.OrderBy(i=>i.GetType().Name))
{
hash.Add(item);
}
return hash.ToHashCode();
}
public override int GetHashCode() => HashCode;
public IVoxelInstance<TAttrribute> CreateDefaultInstance() => new VoxelInstance<TAttrribute>(this);
}
public sealed class VoxelDefinition<TAttrribute, TBitBacking> : VoxelDefinition<TAttrribute>, IEquatable<VoxelDefinition<TAttrribute,TBitBacking>>
where TAttrribute : class where TBitBacking : unmanaged, IBinaryInteger<TBitBacking>
{
public readonly IReadOnlyDictionary<Type, AttributeDescriptor<TAttrribute, TBitBacking>> Descriptor;
public VoxelDefinition(string name, IReadOnlyDictionary<Type, TAttrribute> attributes, IReadOnlyDictionary<Type, AttributeDescriptor<TAttrribute, TBitBacking>> descriptor) : base(name,attributes)
{
Descriptor = descriptor;
}
// private Dictionary<Type, AttributeDescriptor> _descriptors =descripters;
public static VoxelDefinition<TAttrribute, TBitBacking> Create(Action<VoxelDefinitionBuilder<TAttrribute, TBitBacking>> config)
{
var builder = new VoxelDefinitionBuilder<TAttrribute, TBitBacking>();
config(builder);
return builder.Build();
}
// public T? GetAttribute<T>() where T : TAttrribute => Attributes.TryGetValue(typeof(T), out var v) ? (T)v : default;
public Option<T> GetAttribute<T>(TBitBacking data) where T : TAttrribute, IBitBacking<T, TAttrribute, TBitBacking>
{
if (Descriptor.TryGetValue(typeof(T), out var d))
{
if (d.Backing == AttributeBacking.Bits)
{
var attr = T.Create(data >> d.BitOffset & ((TBitBacking.One << d.BitLength) - TBitBacking.One));//WIP
return Option<T>.Some(attr);
}
}
return Attributes.TryGetValue(typeof(T), out var v) ? Option<T>.Some((T)v) : Option<T>.None;
}
// public VoxelHandle SetAttributeData<T>(int data, T value) where T : TAttrribute, IBitBacking<T, TAttrribute,TBitBacking>
// {
// // return data | (T.GetBits(value) << );
// throw new NotImplementedException();//TODO Createnew instance if needed or somthign
// }
public IEnumerable<TInterface> GetAttributesOfType<TInterface>()
{
foreach (var attribute in Attributes)
{
if (attribute.Value is TInterface @interface)
{
yield return @interface;
}
}
}
public TBitBacking ComputeBits(TBitBacking existingBits) => ComputeBits(existingBits, Attributes);
public TBitBacking ComputeBits(TBitBacking existingBits, IEnumerable<KeyValuePair<Type,TAttrribute>> keyValuePairs)
{
TBitBacking result = existingBits;
foreach (var item in keyValuePairs.Where(kvp => Descriptor.ContainsKey(kvp.Key)))//TODO Cache Descripto so not to hash twice
{
if (item is IBitBacking<TAttrribute, TBitBacking> bitBacking)
{
Descriptor.TryGetValue(item.Key, out var descriptor);
var bits = bitBacking.GetBits();
result &= ~(((TBitBacking.One << descriptor.BitLength) - TBitBacking.One) << descriptor.BitOffset);//RemoveMask may not be needed
result |= bits << descriptor.BitOffset;
}
}
return result;
}
public VoxelInstanceBuilder<TAttrribute, TBitBacking> CreateBitBuilderInstance()
{
return new VoxelInstanceBuilder<TAttrribute, TBitBacking>(this);
}
public bool Equals(VoxelDefinition<TAttrribute, TBitBacking> other)
{
var valueCompare = EqualityComparer<AttributeDescriptor<TAttrribute,TBitBacking>>.Default;
return base.Equals((VoxelDefinition<TAttrribute>)other) && Descriptor.Count == other.Descriptor.Count && Descriptor.Keys.All(key => other.Descriptor.ContainsKey(key) && valueCompare.Equals(Descriptor[key], other.Descriptor[key]));
}
}
public class VoxelDefinitionConverter<THandle,TLevel,TAttribute, TBitBacking> : JsonConverter<VoxelDefinition<TAttribute, TBitBacking>> where TAttribute : class where TBitBacking : unmanaged , IBinaryInteger<TBitBacking> where THandle : IVoxelHandle<TLevel>
{
private const string Type = "Type";
private const string Data = "Data";
public override VoxelDefinition<TAttribute, TBitBacking> ReadJson(JsonReader reader, Type objectType, VoxelDefinition<TAttribute, TBitBacking> existingValue, bool hasExistingValue, JsonSerializer serializer)
{
var jObj = JObject.Load(reader);
var deffId = jObj[nameof(VoxelDefinition<TAttribute, TBitBacking>.Name)].ToString();
var DescriptorToken = jObj[nameof(VoxelDefinition<TAttribute, TBitBacking>.Descriptor)];
var descriptorDict = new Dictionary<Type, AttributeDescriptor<TAttribute,TBitBacking>>();
if (DescriptorToken is JArray descrorArray) {
foreach (var item in descrorArray)
{
var typeName = item[Type].ToString();
var type = System.Type.GetType(typeName, throwOnError: true);
var dataToken = item[Data];
var descroterr = (AttributeDescriptor<TAttribute,TBitBacking>)serializer.Deserialize(dataToken.CreateReader(), type);
descriptorDict.Add(type, descroterr);
}
}
var attributesToken = jObj[nameof(VoxelDefinition<TAttribute, TBitBacking>.Attributes)];
var attrDict = new Dictionary<Type, TAttribute>();
if (attributesToken is JArray attrArray) {
foreach (var item in attrArray)
{
var typeName = item[Type].ToString();
var type = System.Type.GetType(typeName, throwOnError: true);
var dataToken = item[Data];
var attr = (TAttribute)serializer.Deserialize(dataToken.CreateReader(), type);
attrDict.Add(type, attr);
}
}
return new VoxelDefinition<TAttribute, TBitBacking>(deffId, attrDict,descriptorDict);
}
public override void WriteJson(JsonWriter writer, VoxelDefinition<TAttribute, TBitBacking> value, JsonSerializer serializer)
{
writer.WriteStartObject();
writer.WritePropertyName(nameof(VoxelDefinition<TAttribute, TBitBacking>.Name));
writer.WriteValue(value.Name);
if (value.Descriptor.Count > 0)
{
writer.WritePropertyName(nameof(VoxelDefinition<TAttribute, TBitBacking>.Descriptor));
writer.WriteStartArray();
foreach (var kvp in value.Descriptor)
{
writer.WriteStartObject();
writer.WritePropertyName(Type);
writer.WriteValue(kvp.Key.FullName);//AssemblyQualifiedName
writer.WritePropertyName(Data);
serializer.Serialize(writer, kvp.Value);
writer.WriteEndObject();
}
writer.WriteEndArray();
}
if (value.Attributes.Count > 0)
{
writer.WritePropertyName(nameof(VoxelDefinition<TAttribute, TBitBacking>.Attributes));
writer.WriteStartArray();
foreach (var kvp in value.Attributes)
{
writer.WriteStartObject();
writer.WritePropertyName(Type);
writer.WriteValue(kvp.Key.FullName);//AssemblyQualifiedName
writer.WritePropertyName(Data);
serializer.Serialize(writer, kvp.Value);
writer.WriteEndObject();
}
writer.WriteEndArray();
}
writer.WriteEndObject();
}
}
public class VoxelInstanceConverter<THandle,TLevel,TAttribute, TBitBacking> : JsonConverter<VoxelInstance<TAttribute, TBitBacking>> where TAttribute : class where TBitBacking : unmanaged , IBinaryInteger<TBitBacking> where THandle : IVoxelHandle<TLevel>
{
private const string Type = "Type";
private const string Data = "Data";
private VoxelPalette<THandle, TLevel, VoxelDefinition<TAttribute, TBitBacking>> _registery;
public VoxelInstanceConverter(VoxelPalette<THandle, TLevel, VoxelDefinition<TAttribute, TBitBacking>> registery) {
_registery = registery;
}
public override VoxelInstance<TAttribute, TBitBacking> ReadJson(JsonReader reader, Type objectType, VoxelInstance<TAttribute, TBitBacking> existingValue, bool hasExistingValue, JsonSerializer serializer)
{
var jObj = JObject.Load(reader);
var deffId = jObj[nameof(VoxelInstance<TAttribute, TBitBacking>.Definition)].ToString();
var definition = _registery.Get(item => item.Name == deffId);
if (!definition.HasValue)
{
throw new Exception();
}
var childSerilazers = new JsonSerializerSettings()
{
Converters = serializer.Converters
};
var converter = JsonSerializer.Create(childSerilazers);
jObj["test"].ToObject<VoxelInstance<TAttribute>>(converter);
var attributesToken = jObj[nameof(VoxelInstance<TAttribute, TBitBacking>.Attributes)];
var dict = new Dictionary<Type, TAttribute>();
if (attributesToken is JArray attrArray) {
foreach (var item in attrArray)
{
var typeName = item[Type].ToString();
var type = System.Type.GetType(typeName, throwOnError: true);
var dataToken = item[Data];
var attr = (TAttribute)serializer.Deserialize(dataToken.CreateReader(), type);
dict.Add(type, attr);
}
}
return new(definition.Value, dict.Count > 0 ? dict.ToOption() : None<Dictionary<Type, TAttribute>>.Of());
}
public override void WriteJson(JsonWriter writer, VoxelInstance<TAttribute, TBitBacking> value, JsonSerializer serializer)
{
writer.WriteStartObject();
writer.WritePropertyName(nameof(VoxelInstance<TAttribute, TBitBacking>.Definition));
writer.WriteValue(value.Definition.Name);
if (value.Attributes.HasValue(out var attrbutes) && attrbutes.Count >0)
{
writer.WritePropertyName(nameof(VoxelInstance<TAttribute, TBitBacking>.Attributes));
writer.WriteStartArray();
foreach (var kvp in attrbutes)
{
writer.WriteStartObject();
writer.WritePropertyName(Type);
writer.WriteValue(kvp.Key.FullName);//AssemblyQualifiedName
writer.WritePropertyName(Data);
serializer.Serialize(writer, kvp.Value);
writer.WriteEndObject();
}
writer.WriteEndArray();
}
writer.WriteEndObject();
}
}
public sealed class DefinitionLevel { private DefinitionLevel() { } }
public sealed class PaletteLevel { private PaletteLevel() { } }
public interface IVoxelAttribute { } //Test
public readonly record struct IsSoild(bool Solid) : IVoxelAttribute ;
public record TextureCube(Color Color,int index) : IVoxelAttribute ;
public record Hardness(byte Value) : IVoxelAttribute, IBitBacking<Hardness, IVoxelAttribute,int>
{
public static int BitLength => 8;
public static AttributeDescriptor<IVoxelAttribute,int> AttributeBacking()
{
return new AttributeDescriptor<IVoxelAttribute,int>(Voxels.Registry.AttributeBacking.Bits,BitOffset: 0, BitLength: BitLength);
}
public static Hardness Create(int bits)
{
return new Hardness((byte)(bits & byte.MaxValue));
}
public static int GetBits(Hardness value)
{
return value.Value & byte.MaxValue;
}
int IBitBacking<IVoxelAttribute, int>.GetBits()
{
return GetBits(this);
}
}
// public class Registry
// {
// private List<VoxelDefinition<IVoxelAttribute, int>> voxelInstances = new();
// public VoxelDefinition<IVoxelAttribute, int> GetVoxel(VoxelHandle<DefinitionLevel> voxelHandle)
// {
// return voxelInstances[voxelHandle.Index];
// }
// public VoxelHandle<DefinitionLevel> Register(VoxelDefinition<IVoxelAttribute, int> voxelDefinition)
// {
// var handle = new VoxelHandle<DefinitionLevel>(voxelInstances.Count);
// voxelInstances.Add(voxelDefinition);
// return handle;
// }
// }
public sealed class VoxelPalette<THandle, TLevel, TEntry> : Palette<TEntry> where THandle : IVoxelHandle<TLevel> where TEntry : IEquatable<TEntry>
{
private readonly Func<int, TEntry, THandle> ctor;
public TEntry Get(THandle handle)
{
if (handle.Index < 0)
throw new Exception();
return Get(handle.Index);
}
public VoxelPalette(Func<int, TEntry, THandle> ctor)
{
this.ctor = ctor;
}
public THandle GetOrAddEntry(TEntry entry) => ctor(GetOrAddIndex(entry), entry);
public THandle ModifyHandle(THandle handle, Func<TEntry, TEntry> modify)
{
var oldEntry = Get(handle);
var newEntry = modify(oldEntry);
if (oldEntry.Equals(newEntry))
{
return handle;
}
return GetOrAddEntry(newEntry);
}
}
public sealed class VoxelRegistry<TEntry, TVoxelDeff, THandle,TAttrubuite> : IVoxelRegistry<THandle, TEntry,TVoxelDeff,TAttrubuite> where TEntry : IVoxelInstance<TAttrubuite> where TAttrubuite : class where THandle : IVoxelHandle<DefinitionLevel> where TVoxelDeff : IVoxelDefinition<TAttrubuite> ,IEquatable<TVoxelDeff>
{
private VoxelPalette<THandle,DefinitionLevel, TVoxelDeff> voxelPalette;
public VoxelRegistry(VoxelPalette<THandle, DefinitionLevel, TVoxelDeff> voxelPalette)
{
this.voxelPalette = voxelPalette;
}
public TEntry Get(THandle handle) => (TEntry)voxelPalette.Get(handle).CreateDefaultInstance();
public TVoxelDeff GetDeff(THandle handle) => voxelPalette.Get(handle);
public THandle GetOrAddEntry(TVoxelDeff entry)
{
return voxelPalette.GetOrAddEntry(entry);
}
}
public class VoxelPaletteJsonConverter<THandle, TLevel, TEntry> : JsonConverter
where THandle : IVoxelHandle<TLevel>
where TEntry : IEquatable<TEntry>
{
private readonly Func<int, TEntry, THandle> _ctor;
public VoxelPaletteJsonConverter(Func<int, TEntry, THandle> ctor)
{
_ctor = ctor;
}
public override bool CanConvert(Type objectType)
=> objectType.IsGenericType && objectType.GetGenericTypeDefinition() == typeof(VoxelPalette<,,>);
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
var palette = (VoxelPalette<THandle, TLevel, TEntry>)value!;
writer.WriteStartObject();
writer.WritePropertyName("entries");
writer.WriteStartArray();
for (int i = 0; i < palette.Count; i++)
{
serializer.Serialize(writer, palette.Get(i));
}
writer.WriteEndArray();
// writer.WritePropertyName("count");
// writer.WriteValue(palette.Count);
writer.WriteEndObject();
}
public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
var jObj = JObject.Load(reader);
var entries = jObj["entries"]?.ToObject<List<TEntry>>(serializer) ?? new List<TEntry>();
var palette = new VoxelPalette<THandle, TLevel, TEntry>(_ctor);
foreach (var entry in entries)
{
palette.GetOrAddEntry(entry);
}
return palette;
}
}
public static class PaletteExtensions
{
public static THandle ModifyVoxelHandle<THandle,TLevel,TAttribute, TBitBacking>(
this VoxelPalette<THandle,TLevel,VoxelInstance<TAttribute,TBitBacking>> palette,
THandle handle,
Func<VoxelInstance<TAttribute,TBitBacking>, (VoxelInstance<TAttribute,TBitBacking> entry, TBitBacking newBits)> modify)
where THandle : IVoxelHandle<TLevel>,IVoxelHandle<TLevel, TBitBacking>
where TBitBacking : unmanaged, IBinaryInteger<TBitBacking>
where TAttribute : class
{
TBitBacking data = handle.Data;
var newHandle = palette.ModifyHandle(handle, (old) =>
{
var result = modify(old);
data = result.newBits;
return result.entry;
});
return (THandle)newHandle.WithData(data);
}
}
// public class test
// {
// public void test2()
// {
// var deff = VoxelDefinition<IVoxelAttribute>.Create(builder =>
// {
// builder.Name = "Stone";
// builder.WithAttribute(new Hardness(1));
// });
// var deff2 = deff.WithVariant(config =>
// {
// config.Name = "SandStone";
// config.WithAttribute(new Hardness(2));
// });
// var block = deff.CreateInstance().Set(new Hardness(3)).Build();
// var block2 = deff2.CreateInstance().Modfiy<Hardness>(existing => new Hardness((byte)(existing.Value + 1))).Build();
// // block.SetAttribute(new Hardness(3));ndel.Data = Hardness.GetBits(new Hardness(4));
// //With Bits
// var deff3 = VoxelDefinition<IVoxelAttribute, int>.Create(builder =>
// {
// builder.Name = "Grass";
// builder.WithAttribute(new Hardness(3), Hardness.AttributeBacking() with { BitOffset = 0, Backing = AttributeBacking.Bits });
// builder.WithAttribute(new IsSoild(false));
// });
// var block3 = deff3.CreateInstance().Build();
// // if (block3.GetAttribute<Hardness>(handel.Data).HasValue && block3.GetAttribute<Hardness>(handel.Data).Value == new Hardness(5))
// // {
// // }
// }
// }V<TAttribute>();

View File

@@ -0,0 +1 @@
uid://tphqa7lth1n1

View File

@@ -0,0 +1,64 @@
using System;
using System.Collections.Generic;
using System.Numerics;
namespace SJK.Voxels.Registry;
public class VoxelDefinitionBuilder<TAttrribute> where TAttrribute : class
{
public string Name { get; set; }
protected readonly Dictionary<Type, TAttrribute> Attributes = new();
public VoxelDefinitionBuilder<TAttrribute> WithAttribute<T>(T attr) where T : TAttrribute
{
Attributes[typeof(T)] = attr;
// _descriptors[typeof(T)] = attributeDescriptor;
return this;
}
public virtual VoxelDefinition<TAttrribute> Build()
{
return new VoxelDefinition<TAttrribute>(Name, Attributes);
}
}
public class VoxelDefinitionBuilder<TAttrribute,TBitBacking> : VoxelDefinitionBuilder<TAttrribute> where TAttrribute : class where TBitBacking : unmanaged, IBinaryInteger<TBitBacking>{
private readonly Dictionary<Type, AttributeDescriptor<TAttrribute,TBitBacking>> _descriptor = new();
// private Dictionary<Type, VoxelDefinition<TAttrribute>.AttributeDescriptor> _descriptors = new();
public VoxelDefinitionBuilder<TAttrribute, TBitBacking> WithAttribute<T>(T attr, AttributeDescriptor<TAttrribute, TBitBacking> attributeDescriptor) where T : TAttrribute
{
WithAttribute(attr);
_descriptor[typeof(T)] = attributeDescriptor;
if (!isValidOverlap(attributeDescriptor, _descriptor.Values))
{
throw new Exception();
}
// _descriptors[typeof(T)] = attributeDescriptor;
return this;
}
private bool isValidOverlap(AttributeDescriptor<TAttrribute, TBitBacking> a, IEnumerable<AttributeDescriptor<TAttrribute, TBitBacking>> others)
{
if (a.Backing != AttributeBacking.Bits)
{
return true;
}
int newstart = a.BitOffset;
int newend = a.BitOffset + a.BitOffset - 1;
foreach (var d in others)
{
if (d.Backing != AttributeBacking.Bits)
{
continue;
}
int start = d.BitOffset;
int end = start + d.BitLength - 1;
if (!(newend < start || newstart > end))
{
return false;
}
}
return true;
}
public override VoxelDefinition<TAttrribute,TBitBacking> Build()
{
return new VoxelDefinition<TAttrribute,TBitBacking>(Name, Attributes,_descriptor);
}
}

View File

@@ -0,0 +1 @@
uid://bgd33blsocwb6

View File

@@ -0,0 +1,38 @@
using System;
using System.Numerics;
namespace SJK.Voxels.Registry;
public readonly struct VoxelHandle<TLevel>(int index) : IVoxelHandle<TLevel>, IEquatable<VoxelHandle<TLevel>>
{
public readonly int Index { get; } = index;
public bool Equals(IVoxelHandle<TLevel> other) => other is VoxelHandle<TLevel> otherHandle && Equals(otherHandle);
public bool Equals(VoxelHandle<TLevel> other) => Index == other.Index;
}
public interface IVoxelHandle<TLevel> : IEquatable<IVoxelHandle<TLevel>> {
int Index { get; }
}
public interface IVoxelHandle<TLevel, TBitBacking> : IEquatable<IVoxelHandle<TLevel, TBitBacking>> {
TBitBacking Data { get; }
IVoxelHandle<TLevel, TBitBacking> WithData(TBitBacking bitBacking);
}
public readonly struct VoxelHandle<TLevel, TBitBacking>(int index, TBitBacking data) : IVoxelHandle<TLevel>,IVoxelHandle<TLevel,TBitBacking>, IEquatable<VoxelHandle<TLevel, TBitBacking>> where TBitBacking : unmanaged, IBinaryInteger<TBitBacking>
{
public readonly int Index { get; } = index;
public readonly TBitBacking Data { get; } = data;
public bool Equals(IVoxelHandle<TLevel> other) => other is VoxelHandle<TLevel, TBitBacking> otherHandle && Equals(otherHandle);
public bool Equals(VoxelHandle<TLevel, TBitBacking> other) => Index == other.Index && Data == other.Data;
public bool Equals(IVoxelHandle<TLevel, TBitBacking> other) => other is VoxelHandle<TLevel, TBitBacking> other2 && Equals(other2);
public VoxelHandle<TLevel, TBitBacking> WithData(TBitBacking bitBacking) => new VoxelHandle<TLevel, TBitBacking>(Index, bitBacking);
IVoxelHandle<TLevel, TBitBacking> IVoxelHandle<TLevel, TBitBacking>.WithData(TBitBacking bitBacking) => WithData(bitBacking);
}

View File

@@ -0,0 +1 @@
uid://4w8vs8tnuopn

View File

@@ -0,0 +1,229 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Godot;
using SJK.Functional;
namespace SJK.Voxels.Registry;
public interface IVoxelInstance<TAttribute> where TAttribute : class
{
public IVoxelDefinition<TAttribute> Definition { get; }
}
public sealed class VoxelInstance<TAttribute> : IVoxelInstance<TAttribute> , IEquatable<VoxelInstance<TAttribute>> where TAttribute : class
{
public IVoxelDefinition<TAttribute> Definition { get; }
public readonly IOption<Dictionary<Type, TAttribute>> Attributes;
public readonly int HashCode;
public VoxelInstance(IVoxelDefinition<TAttribute> definition, IOption<Dictionary<Type, TAttribute>> attributes)
{
Definition = definition;
Attributes = attributes;
HashCode = ComputeHashCode();
}
public VoxelInstance(VoxelDefinition<TAttribute> definition, Dictionary<Type, TAttribute>? attributes = null)
{
Definition = definition;
Attributes = attributes.ToOption();
HashCode = ComputeHashCode();
}
private int ComputeHashCode()
{
HashCode hash = new HashCode();
hash.Add(Definition.GetHashCode());
Attributes.IfSome(attributes => attributes.Values.Order().ToList().ForEach(hash.Add));
return hash.ToHashCode();
}
// public Option<T> GetAttribute<T>(int data) where T : TAttribute, IBitBacking<T,TAttribute,> =>
// Attributes.HasValue(out var att) && att.TryGetValue(typeof(T), out var v)
// ? Option<T>.Some((T)v)
// : Definition.GetAttribute<T>(data);
public T GetAttribute<T>(Func<T> ctor) where T : TAttribute =>
Attributes.HasValue(out var att) && att.TryGetValue(typeof(T), out var v)
? (T)v
: Definition.GetAttribute<T>(ctor);
public IEnumerable<TInterface> GetAttributesOfType<TInterface>()
{
throw new NotImplementedException();
// var set = new HashSet<Type>();
// if (Attributes.HasValue(out var attributes))
// {
// foreach (var item in attributes)
// {
// if (item.Value is TInterface @interface)
// {
// set.Add(item.Key);
// yield return @interface;
// }
// }
// }
// foreach (var attributeKVP in Definition.Attributes)
// {
// if (attributeKVP.Value is not TInterface @interface || set.Contains(attributeKVP.Key))
// {
// continue;
// }
// yield return @interface;
// }
}
/// <summary>
/// Creates a builder initialized with this instances attributes.
/// </summary>
public VoxelInstanceBuilder<TAttribute> ToBuilder() =>
new VoxelInstanceBuilder<TAttribute>(this);
public override bool Equals(object obj) => obj is VoxelInstance<TAttribute> other && Equals(other);
public bool Equals(VoxelInstance<TAttribute> other) {
if (HashCode != other.HashCode)
{
return false;
}
if (ReferenceEquals(this, other))
{
return true;
}
if (!Definition.Equals(other.Definition))
{
return false;
}
if (!Attributes.HasValue() && !other.Attributes.HasValue())
{
return true;
}
if (!Attributes.HasValue(out var a) || !other.Attributes.HasValue(out var b))
{
return false;
}
if (a.Count != b.Count)
{
return false;
}
var valueCompare = EqualityComparer<TAttribute>.Default;
return a.Keys.All(key => b.ContainsKey(key) && valueCompare.Equals(a[key], b[key]));
}
public override int GetHashCode() => HashCode;
// {
// var hash = Definition.GetHashCode();
// Attributes.IfSome(some =>
// {
// foreach (var kvp in some.OrderBy(k => k.Key.FullName))
// {
// hash = HashCode.Combine(hash, kvp.Key.GetHashCode(), kvp.Value.GetHashCode());
// }
// });
// return hash;
// }
}
public sealed class VoxelInstance<TAttribute,TBitBacking>: IVoxelInstance<TAttribute>, IEquatable<VoxelInstance<TAttribute,TBitBacking>> where TAttribute : class where TBitBacking : unmanaged, IBinaryInteger<TBitBacking>
{
public VoxelDefinition<TAttribute,TBitBacking> Definition { get; }
IVoxelDefinition<TAttribute> IVoxelInstance<TAttribute>.Definition => Definition;
public readonly IOption<Dictionary<Type, TAttribute>> Attributes;
public readonly int HashCode;
public VoxelInstance(VoxelDefinition<TAttribute, TBitBacking> definition, IOption<Dictionary<Type, TAttribute>> attributes)
{
Definition = definition;
Attributes = attributes;
HashCode = ComputeHashCode();
}
private int ComputeHashCode()
{
HashCode hash = new HashCode();
hash.Add(Definition.GetHashCode());
Attributes.IfSome(attributes => attributes.Values.Order().ToList().ForEach(hash.Add));
return hash.ToHashCode();
}
public Option<T> GetAttribute<T>(TBitBacking data) where T : TAttribute, IBitBacking<T,TAttribute,TBitBacking> =>
Attributes.HasValue(out var att) && att.TryGetValue(typeof(T), out var v)
? Option<T>.Some((T)v)
: Definition.GetAttribute<T>(data);
/// <summary>
/// Does not respect Data, only based of Deffination
/// </summary>
/// <typeparam name="TInterface"></typeparam>
/// <returns></returns>
public IEnumerable<TInterface> GetAttributesOfType<TInterface>()
{
var set = new HashSet<Type>();
if (Attributes.HasValue(out var attributes))
{
foreach (var item in attributes)
{
if (item.Value is TInterface @interface)
{
yield return @interface;
set.Add(item.Key);
}
}
}
foreach (var attributeKVP in Definition.Attributes)
{
if (attributeKVP.Value is not TInterface @interface || set.Contains(attributeKVP.Key))
{
continue;
}
yield return @interface;
}
}
public TBitBacking ComputeBits(TBitBacking existingBits) =>Attributes.Match(some =>Definition.ComputeBits(Definition.ComputeBits(existingBits),some),()=>Definition.ComputeBits(existingBits));
/// <summary>
/// Creates a builder initialized with this instances attributes.
/// </summary>
public VoxelInstanceBuilder<TAttribute, TBitBacking> ToBuilder(TBitBacking existingBits) =>//<THandle,TLevel>() where THandle : IVoxelHandle<TLevel,TBitBacking> =>
new VoxelInstanceBuilder<TAttribute, TBitBacking>(this,existingBits);
public bool Equals(VoxelInstance<TAttribute, TBitBacking> other)
{
if (HashCode != other.HashCode)
{
return false;
}
if (ReferenceEquals(this, other))
{
return true;
}
if (!Definition.Equals(other.Definition))
{
return false;
}
if (!Attributes.HasValue() && !other.Attributes.HasValue())
{
return true;
}
if (!Attributes.HasValue(out var a) || !other.Attributes.HasValue(out var b))
{
return false;
}
if (a.Count != b.Count)
{
return false;
}
var valueCompare = EqualityComparer<TAttribute>.Default;
return a.Keys.All(key => b.ContainsKey(key) && valueCompare.Equals(a[key], b[key]));
}
public override bool Equals(object obj) => obj is VoxelInstance<TAttribute, TBitBacking> other && Equals(other);
public override int GetHashCode() => HashCode;
// {
// var hash = Definition.GetHashCode();
// Attributes.IfSome(some =>
// {
// foreach (var kvp in some.OrderBy(k => k.Key.FullName))
// {
// hash = HashCode.Combine(hash, kvp.Key.GetHashCode(), kvp.Value.GetHashCode());
// }
// });
// return hash;
// }
}

View File

@@ -0,0 +1 @@
uid://bes4tnuxpkexq

View File

@@ -0,0 +1,125 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using SJK.Functional;
namespace SJK.Voxels.Registry;
/// <summary>
/// Builder for creating modified VoxelInstances without mutating the original.
/// </summary>
public sealed class VoxelInstanceBuilder<TAttribute> where TAttribute : class
{
private readonly IVoxelDefinition<TAttribute> _definition;
private Dictionary<Type, TAttribute> _attributes;
public VoxelInstanceBuilder(VoxelInstance<TAttribute> source)
{
_definition = source.Definition;
// Copy only if source had attributes
_attributes = new Dictionary<Type, TAttribute>(source.Attributes.Or([]));
}
public VoxelInstanceBuilder(VoxelDefinition<TAttribute> source)
{
_definition = source;
_attributes = [];
}
public VoxelInstanceBuilder<TAttribute> Set<T>(T attr) where T : TAttribute
{
if (attr.Equals(_definition.GetAttribute<T>()))
{
Remove<T>();
}
else
{
_attributes[typeof(T)] = attr;
}
return this;
}
public VoxelInstanceBuilder<TAttribute> Modfiy<T>(Func<T, T> map) where T : TAttribute
{
if (_attributes.TryGetValue(typeof(T), out var attribute))
{
var newAttr = map((T)attribute);
Set(newAttr);
}
return this;
}
public VoxelInstanceBuilder<TAttribute> Remove<T>() where T : TAttribute
{
_attributes.Remove(typeof(T));
return this;
}
public VoxelInstance<TAttribute> Build() =>
new VoxelInstance<TAttribute>(_definition, _attributes.ToOption());
}
public sealed class VoxelInstanceBuilder<TAttribute,TBitBacking> where TAttribute : class where TBitBacking : unmanaged,IBinaryInteger<TBitBacking>
{
private readonly VoxelDefinition<TAttribute,TBitBacking> _definition;
private readonly Dictionary<Type, TAttribute> _attributes;
private readonly TBitBacking existingBits;
public TBitBacking NewBits;
public VoxelInstanceBuilder(VoxelInstance<TAttribute, TBitBacking> source, TBitBacking existingData)
{
_definition = source.Definition;
// Copy only if source had attributes
_attributes = new Dictionary<Type, TAttribute>(source.Attributes.Or([]));
this.existingBits = existingData;
this.NewBits = existingBits;
}
public VoxelInstanceBuilder(VoxelDefinition<TAttribute,TBitBacking> source)
{
_definition = source;
_attributes = [];
}
public VoxelInstanceBuilder<TAttribute,TBitBacking> Set<T>(T attr) where T : TAttribute
{
if (_definition.Descriptor.TryGetValue(typeof(T), out var descriptor)
&& descriptor.Backing == AttributeBacking.Bits
&& attr is IBitBacking<TAttribute, TBitBacking> bitBacking)
{
var bits = bitBacking.GetBits();
NewBits &= ~(((TBitBacking.One << descriptor.BitLength) - TBitBacking.One) << descriptor.BitOffset);//RemoveMask may not be needed
NewBits |= bits << descriptor.BitOffset;
return this;
}
if (attr.Equals(_definition.GetAttribute<T>()))
{
Remove<T>();
}
else
{
_attributes[typeof(T)] = attr;
}
return this;
}
public VoxelInstanceBuilder<TAttribute,TBitBacking> Modfiy<T>(Func<T, T> map) where T : TAttribute
{
if (_attributes.TryGetValue(typeof(T), out var attribute))
{
var newAttr = map((T)attribute);
Set(newAttr);
}
return this;
}
public VoxelInstanceBuilder<TAttribute,TBitBacking> Remove<T>() where T : TAttribute
{
_attributes.Remove(typeof(T));
return this;
}
// [Export]
public VoxelInstance<TAttribute, TBitBacking> Build(out TBitBacking newBits)
{
newBits = NewBits;
return new VoxelInstance<TAttribute, TBitBacking>(_definition, _attributes.ToOption());
}
}

View File

@@ -0,0 +1 @@
uid://b3bek2f2qoo8l

View File

@@ -0,0 +1,386 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Godot;
using Microsoft.Data.Sqlite;
using SqlKata;
using SqlKata.Compilers;
namespace SJK.Voxels;
public interface ISpatialTree<T>// where T : ISpatialEntry
{
void Insert(T entry);
void Remove(T entry);
IEnumerable<T> Query(Aabb region);
}
public interface ISpatialEntry
{
Guid Guid { get; }
Aabb Bounds { get; }
}
public sealed class SpatialDataLayer<TEntry> : ISpatialTree<TEntry> where TEntry : class, ISpatialEntry
{
private Dictionary<Guid, TEntry> _entries = new();
private record struct Entry(Guid Guid, Aabb Bounds) : ISpatialEntry;
GridSpatialIndex<Entry, OctreeEntryStore<Entry>> gridSpatialIndex;
public SpatialDataLayer(int gridSize = 128)
{
gridSpatialIndex = new(() => new OctreeEntryStore<Entry>(new Aabb(Vector3.Zero, Vector3.One * gridSize)), gridSize);
}
public void Insert(TEntry entry)
{
gridSpatialIndex.Insert(new(entry.Guid, entry.Bounds));
_entries.Add(entry.Guid, entry);
}
// public async ValueTask Save(string @namespace, IDataStore dataStore, IDataSerializer<TEntry[]> serializer)
// {
// var data = await serializer.SerializeAsync(_entries.Values.ToArray());
// await dataStore.WriteAsync(new DataKey(@namespace, nameof(_entries)), data);
// }
// public async ValueTask Load(string @namespace, IDataStore dataStore, IDataSerializer<TEntry[]> serializer)
// {
// gridSpatialIndex.Clear();
// _entries.Clear();
// var data = await dataStore.ReadAsync(new DataKey(@namespace, nameof(_entries)));
// var e = await serializer.DeserializeAsync(data.Value);
// e.ToList().ForEach(Insert);
// }
public IEnumerable<TEntry> Query(Aabb region)
{
return gridSpatialIndex.Query(region).Where(e => _entries.ContainsKey(e.Guid)).Select(e => _entries[e.Guid]);
}
public void Remove(TEntry entry)
{
gridSpatialIndex.Remove(new(entry.Guid, entry.Bounds));
_entries.Remove(entry.Guid);
}
}
public class CollectionSpatialTree<TEntry> : ISpatialTree<TEntry> where TEntry : ISpatialEntry
{
private Dictionary<Guid,TEntry> entries = new();
public void Insert(TEntry entry)
{
entries[entry.Guid] = entry;
}
public IEnumerable<TEntry> Query(Aabb region)
{
foreach (var item in entries)
{
if (region.Intersects(item.Value.Bounds))
{
yield return item.Value;
}
}
}
public void Remove(TEntry entry)
{
entries.Remove(entry.Guid);
}
}
public class GridSpatialIndex<TEntry, TChunkSpatialTree> : ISpatialTree<TEntry> where TEntry : ISpatialEntry where TChunkSpatialTree : ISpatialTree<TEntry>
{
private readonly int _cellSize;
private readonly Func<TChunkSpatialTree> factory;
private readonly Dictionary<Vector3I, TChunkSpatialTree> _cells = new();
public GridSpatialIndex(Func<TChunkSpatialTree> factory, int cellSize = 32)
{
this._cellSize = cellSize;
this.factory = factory;
}
private Vector3I GetCellCoord(Vector3 position)
{
int cx = (int)MathF.Floor(position.X / _cellSize);
int cy = (int)MathF.Floor(position.Y / _cellSize);
int cz = (int)MathF.Floor(position.Z / _cellSize);
return new(cx, cy, cz);
}
private Vector3I[] GetOverlappingCells(Aabb bounds)
{
var minCell = GetCellCoord(bounds.Min);
var maxCell = GetCellCoord(bounds.Max);
var cells = new List<Vector3I>();
for (int x = minCell.X; x <= maxCell.X; x++)
for (int y = minCell.Y; y <= maxCell.Y; y++)
for (int z = minCell.Z; z <= maxCell.Z; z++)
cells.Add(new(x, y, z));
return cells.ToArray();
}
public void Insert(TEntry entry)
{
foreach (var cell in GetOverlappingCells(entry.Bounds))
{
if (!_cells.TryGetValue(cell, out var tree))
{
_cells[cell] = tree = factory();
}
tree.Insert(entry);
}
}
public IEnumerable<TEntry> Query(Aabb region)
{
var visted = new HashSet<TEntry>();
foreach (var cell in GetOverlappingCells(region))
{
if (_cells.TryGetValue(cell, out var tree))
{
foreach (var entry in tree.Query(region))
{
if (visted.Add(entry))
{
yield return entry;
}
}
}
}
}
public void Remove(TEntry entry)
{
foreach (var cell in GetOverlappingCells(entry.Bounds))
{
if (_cells.TryGetValue(cell, out var tree))
{
tree.Remove(entry);
}
}
}
public void Clear()
{
_cells.Clear();
}
}
public readonly record struct Aabb
{
public readonly Vector3 Min { get; }
public readonly Vector3 Max { get; }
public Aabb(Vector3 min, Vector3 max)
{
Min = min.Min(max);
Max = min.Max(max);
}
public bool Intersects(Aabb other)
{
return !(Max.X < other.Min.X || Min.X > other.Max.X ||
Max.Y < other.Min.Y || Min.Y > other.Max.Y ||
Max.Z < other.Min.Z || Min.Z > other.Max.Z);
}
public bool Contains(Vector3 point)
{
return point.X >= Min.X && point.X <= Max.X &&
point.Y >= Min.Y && point.Y <= Max.Y &&
point.Z >= Min.Z && point.Z <= Max.Z;
}
public Aabb Expand(Vector3I value) =>
new Aabb(
Min.Min(value),
Max.Max(value)
);
public Vector3 Center => (Min + Max) * 0.5f;
public Vector3 Size => Max - Min;
}
public sealed class OctreeEntryStore<T> : ISpatialTree<T> where T : ISpatialEntry
{
private readonly OctreeNode<T> _root;
public OctreeEntryStore(Aabb worldBounds, int maxDepth = 8, int maxPerNode = 16)
{
_root = new OctreeNode<T>(worldBounds, maxDepth, maxPerNode);
}
public void Insert(T entry) => _root.Insert(entry);
public bool Remove(T entry) => _root.Remove(entry);
public IEnumerable<T> Query(Aabb bounds) => _root.Query(bounds);
// public IEnumerable<T> QuerySphere(Vector3 center, float radius) => _root.QuerySphere(center, radius);
void ISpatialTree<T>.Remove(T entry)
{
this.Remove(entry);
}
// --- Internal Octree Node ---
private sealed class OctreeNode<TNode> where TNode : ISpatialEntry
{
private readonly Aabb _bounds;
private readonly int _maxDepth;
private readonly int _maxPerNode;
private readonly int _depth;
private List<TNode>? _entries;
private OctreeNode<TNode>[]? _children;
public OctreeNode(Aabb bounds, int maxDepth, int maxPerNode, int depth = 0)
{
_bounds = bounds;
_maxDepth = maxDepth;
_maxPerNode = maxPerNode;
_depth = depth;
_entries = new List<TNode>();
}
public void Insert(TNode entry)
{
// If we have children, try to pass entry down.
if (_children != null)
{
foreach (var child in _children)
{
if (child._bounds.Intersects(entry.Bounds))
{
child.Insert(entry);
return;
}
}
}
// Otherwise, keep it here.
_entries ??= new List<TNode>();
_entries.Add(entry);
// Subdivide if too many and not too deep
if (_entries.Count > _maxPerNode && _depth < _maxDepth)
Subdivide();
}
public bool Remove(TNode entry)
{
bool removed = _entries?.Remove(entry) ?? false;
if (_children != null)
{
foreach (var child in _children)
removed |= child.Remove(entry);
}
return removed;
}
public IEnumerable<TNode> Query(Aabb bounds)
{
if (!_bounds.Intersects(bounds))
yield break;
if (_entries != null)
{
foreach (var e in _entries)
if (e.Bounds.Intersects(bounds))
yield return e;
}
if (_children != null)
{
foreach (var child in _children)
{
foreach (var e in child.Query(bounds))
yield return e;
}
}
}
// public IEnumerable<TNode> QuerySphere(Vector3 center, float radius)
// {
// // Quick reject if AABB doesn't overlap sphere
// if (!AabbSphereIntersect(_bounds, center, radius))
// yield break;
// if (_entries != null)
// {
// foreach (var e in _entries)
// {
// var distSq = e.Position.DistanceSquaredTo(center);
// if (distSq <= radius * radius)
// yield return e;
// }
// }
// if (_children != null)
// {
// foreach (var child in _children)
// {
// foreach (var e in child.QuerySphere(center, radius))
// yield return e;
// }
// }
// }
private void Subdivide()
{
if (_children != null) return;
_children = new OctreeNode<TNode>[8];
var center = _bounds.Center;
var size = _bounds.Size * 0.5f;
int i = 0;
for (int x = 0; x <= 1; x++)
for (int y = 0; y <= 1; y++)
for (int z = 0; z <= 1; z++)
{
var min = new Vector3(
x == 0 ? _bounds.Min.X : center.X,
y == 0 ? _bounds.Min.Y : center.Y,
z == 0 ? _bounds.Min.Z : center.Z);
var max = min + size;
_children[i++] = new OctreeNode<TNode>(new Aabb(min, max), _maxDepth, _maxPerNode, _depth + 1);
}
// Redistribute current entries
var oldEntries = _entries!;
_entries = new List<TNode>();
foreach (var entry in oldEntries)
Insert(entry);
}
private static bool AabbSphereIntersect(Aabb aabb, Vector3 center, float radius)
{
float sqDist = 0f;
for (int i = 0; i < 3; i++)
{
float v = center[i];
if (v < aabb.Min[i]) sqDist += (aabb.Min[i] - v) * (aabb.Min[i] - v);
if (v > aabb.Max[i]) sqDist += (v - aabb.Max[i]) * (v - aabb.Max[i]);
}
return sqDist <= radius * radius;
}
}
}
public readonly struct SpatialBounds
{
public readonly Vector3 Center;
public readonly Vector3 Extents;
public SpatialBounds(Vector3 center, Vector3 extents)
{
Center = center;
Extents = extents;
}
public static SpatialBounds Point(Vector3 position) => new(position, Vector3.Zero);
public Aabb ToAabb() => new(Center - Extents, Center + Extents);
public bool Intersects(Aabb region) => region.Intersects(ToAabb());
}
public readonly struct SpatialEntry<T>
{
public readonly Guid Id;
// public readonly Aabb Bounds;
public readonly SpatialBounds Bounds;
public readonly T Value;
// public SpatialEntry(Guid id, T value, Aabb bounds)
public SpatialEntry(Guid id, T value, SpatialBounds bounds)
{
Id = id;
Value = value;
Bounds = bounds;
}
}

View File

@@ -0,0 +1 @@
uid://dik0ryeup2wah

View File

@@ -0,0 +1,14 @@
[gd_scene load_steps=2 format=3 uid="uid://cchpoo5dqwn2w"]
[ext_resource type="Script" uid="uid://kdg8pxsdacor" path="res://Voxels/UVWrapVisualizer.cs" id="1_m0iwv"]
[node name="TestWraper" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
scale = Vector2(12.215, 12.215)
script = ExtResource("1_m0iwv")
ChunkSize = 64

View File

@@ -0,0 +1,111 @@
using Godot;
using SJK.Voxels;
using System;
public partial class UVWrapVisualizer : Control
{
public enum WrapperType
{
Normal,
Twisted
}
[Export] public int WorldSize = 8;
[Export] public int ChunkSize = 16;
[Export] public int RenderChunks = 4;
[Export] public WrapperType WrapMode = WrapperType.Twisted;
private ImageTexture texture;
public override void _Ready()
{
GenerateTexture();
}
private IWorldWrapper wrapper = new EndlessSizeXZWorld(10);
private void GenerateTexture()
{
int texSize = WorldSize * ChunkSize * RenderChunks;
var image = Godot.Image.CreateEmpty(texSize, texSize, false, Image.Format.Rgb8);
wrapper = WrapMode switch
{
WrapperType.Normal => new ToroidalWrapping(Vector3I.One * WorldSize),
WrapperType.Twisted => new MirroredZWrappingXOffset(Vector3I.One * WorldSize),
_ => throw new Exception(),
};
GD.Print(wrapper.GetType());
for (int gz = 0; gz < texSize; gz++)
{
for (int gx = 0; gx < texSize; gx++)
{
Vector2I global = new Vector2I(gx - texSize / 2, gz - texSize / 2); // center origin
var v3 = wrapper.Wrap(new Vector3I(global.X, 1, global.Y));
Vector2I local = new(v3.X, v3.Z);
bool flip = false;
// switch (WrapMode)
// {
// case WrapperType.Twisted:
// (local, flip) = TwistedWrapInt(global, WorldSize, WorldSize);
// break;
// default:
// local = new Vector2I(Mod(global.X, WorldSize), Mod(global.Y, WorldSize));
// flip = false;
// break;
// }
// Encode local position into RGB (U and V channels)
float u = local.X / (float)WorldSize;
float v = local.Y / (float)WorldSize;
Color color = new Color(u, v, flip ? 1.0f : 0.0f);
image.SetPixel(gx, gz, color);
}
}
texture = ImageTexture.CreateFromImage(image);
QueueRedraw();
}
public override void _Draw()
{
if (texture != null)
{
DrawTexture(texture, Vector2.Zero);
}
}
// ========== Wrapping Logic ==========
private (Vector2I local, bool flipped) TwistedWrapInt(Vector2I global, int W, int H)
{
int gx = global.X;
int gy = global.Y;
int yWrap = FloorDiv(gy, H);
bool flip = (yWrap & 1) != 0;
int tileY = gy % H;
if (tileY < 0) tileY += H;
int ly = flip ? (H - 1 - tileY) : tileY;
int xOffset = flip ? W / 2 : 0;
int wrappedX = gx + xOffset;
int tileX = wrappedX % W;
if (tileX < 0) tileX += W;
int lx = flip ? (W - 1 - tileX) : tileX;
return (new Vector2I(lx, ly), flip);
}
private int FloorDiv(int a, int b)
{
return (a >= 0) ? a / b : ((a + 1) / b) - 1;
}
private int Mod(int a, int b)
{
int r = a % b;
return r < 0 ? r + b : r;
}
}

View File

@@ -0,0 +1 @@
uid://kdg8pxsdacor

View File

@@ -0,0 +1,194 @@
using System;
using System.Collections;
using System.Collections.Generic;
public interface IVoxelQuery : IEnumerable<VoxelPos>
{
IVoxelQuery Union(IVoxelQuery other);
IVoxelQuery Exclude(IVoxelQuery other);
IVoxelQuery Intersect(IVoxelQuery other);
IVoxelQuery Where(Func<VoxelPos, bool> predicate);
IVoxelQuery Offset(int x, int y, int z);
// IVoxelQuery Rotate(float yaw, float pitch, float roll);
}
public abstract class VoxelQueryBase : IVoxelQuery
{
public abstract IEnumerator<VoxelPos> GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public IVoxelQuery Where(Func<VoxelPos, bool> predicate)
=> new FilterQuery(this, predicate);
public IVoxelQuery Union(IVoxelQuery other)
=> new UnionQuery(this, other);
public IVoxelQuery Intersect(IVoxelQuery other)
=> new IntersectQuery(this, other);
public IVoxelQuery Exclude(IVoxelQuery other)
=> new ExcludeQuery(this, other);
public IVoxelQuery Offset(int x, int y, int z)
=> new TransformQuery(this, pos => new VoxelPos(pos.X + x, pos.Y + y, pos.Z + z));
// public IVoxelQuery Rotate(float yaw, float pitch, float roll)
// => new TransformQuery(this, pos => RotatePoint(pos, yaw, pitch, roll));
// protected static VoxelPos RotatePoint(VoxelPos pos, float yaw, float pitch, float roll)
// {
// // simple rotation around origin
// var rx = (int)Math.Round(pos.X * Math.Cos(yaw) - pos.Z * Math.Sin(yaw));
// var rz = (int)Math.Round(pos.X * Math.Sin(yaw) + pos.Z * Math.Cos(yaw));
// return new(rx, pos.Y, rz);
// }
}
public sealed class TransformQuery : VoxelQueryBase
{
private readonly IVoxelQuery source;
private readonly Func<VoxelPos,VoxelPos> offset;
public TransformQuery(IVoxelQuery source, Func<VoxelPos,VoxelPos> offset)
{
this.source = source;
this.offset = offset;
}
public override IEnumerator<VoxelPos> GetEnumerator()
{
foreach (var pos in source)
{
yield return offset(pos);
}
}
}
public sealed class SphereQuery : VoxelQueryBase
{
private readonly VoxelPos _center;
private readonly int _radius;
public SphereQuery(VoxelPos center, int radius)
{
_center = center;
_radius = radius;
}
public override IEnumerator<VoxelPos> GetEnumerator()
{
int r2 = _radius * _radius;
for (int x = -_radius; x <= _radius; x++)
for (int y = -_radius; y <= _radius; y++)
for (int z = -_radius; z <= _radius; z++)
{
if (x * x + y * y + z * z <= r2)
yield return new VoxelPos(_center.X + x, _center.Y + y, _center.Z + z);
}
}
}
public sealed class BoxQuery : VoxelQueryBase
{
private readonly VoxelPos _min, _max;
public BoxQuery(VoxelPos min, VoxelPos max)
{
_min = min;
_max = max;
}
public override IEnumerator<VoxelPos> GetEnumerator()
{
for (int x = _min.X; x <= _max.X; x++)
for (int y = _min.Y; y <= _max.Y; y++)
for (int z = _min.Z; z <= _max.Z; z++)
yield return new(x, y, z);
}
}
public sealed class FilterQuery : VoxelQueryBase
{
private readonly IVoxelQuery _source;
private readonly Func<VoxelPos, bool> _predicate;
public FilterQuery(IVoxelQuery source, Func<VoxelPos, bool> predicate)
{
_source = source;
_predicate = predicate;
}
public override IEnumerator<VoxelPos> GetEnumerator()
{
foreach (var pos in _source)
if (_predicate(pos))
yield return pos;
}
}
public sealed class UnionQuery : VoxelQueryBase
{
private readonly IVoxelQuery _a, _b;
public UnionQuery(IVoxelQuery a, IVoxelQuery b)
{
_a = a;
_b = b;
}
public override IEnumerator<VoxelPos> GetEnumerator()
{
var seen = new HashSet<VoxelPos>();
foreach (var pos in _a)
if (seen.Add(pos)) yield return pos;
foreach (var pos in _b)
if (seen.Add(pos)) yield return pos;
}
}
public sealed class IntersectQuery : VoxelQueryBase
{
private readonly IVoxelQuery _a, _b;
public IntersectQuery(IVoxelQuery a, IVoxelQuery b)
{
_a = a;
_b = b;
}
public override IEnumerator<VoxelPos> GetEnumerator()
{
var b = new HashSet<VoxelPos>(_b);
foreach (var pos in _a)
if (b.Contains(pos)) yield return pos;
}
}
public sealed class ExcludeQuery : VoxelQueryBase
{
private readonly IVoxelQuery _source, _exclude;
public ExcludeQuery(IVoxelQuery source, IVoxelQuery exclude)
{
_source = source;
_exclude = exclude;
}
public override IEnumerator<VoxelPos> GetEnumerator()
{
var excludeSet = new HashSet<VoxelPos>(_exclude);
foreach (var pos in _source)
if (!excludeSet.Contains(pos))
yield return pos;
}
}
public static class VoxelQueryExtensions
{
public static IEnumerable<(VoxelPos pos, TVoxel voxel)> WithData<TVoxel>(
this IVoxelQuery query,
Func<VoxelPos, TVoxel> source)
{
foreach (var pos in query)
{
var voxel = source(pos);
yield return (pos, voxel);
}
}
// public static IEnumerable<(ChunkPos<TChunkSize> ChunkPos, VoxelPos<TChunkSize> LocalPos)> ToChunkLocal<TChunkSize>(this IEnumerable<VoxelPos> self) where TChunkSize : IChunkSize<TChunkSize>
// {
// foreach (var pos in self)
// {
// yield return pos.ToVoxelPos<TChunkSize>();
// }
// }
}

View File

@@ -0,0 +1 @@
uid://f4gkrcovyyc

View File

@@ -0,0 +1,91 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using ChickenGameTest;
using Godot;
using SJK.Functional;
using SJK.Voxels.Registry;
namespace SJK.Voxels;
public interface IWorldChunk
{
bool IsLoaded {get;}
}
public interface IWorldChunk<TVoxel,TChunkSize> :IWorldChunk, IReadOnlyVoxelChunk<TVoxel,TChunkSize> where TChunkSize : IChunkSize<TChunkSize>
{
ChunkPos<TChunkSize> ChunkPos {get;set;}
Option<T> GetCustomData<T>() where T : IChunkData;
void SetCustomData<T>(T value)where T : IChunkData;
TVoxel GetVoxelAt(VoxelPos<TChunkSize> voxelPos);
void SetVoxelAt(VoxelPos<TChunkSize> voxelPos, TVoxel voxel);
}
public class WorldChunk<TVoxel,TChunkSize> : IWorldChunk<TVoxel,TChunkSize> where TChunkSize : IChunkSize<TChunkSize> where TVoxel : IEquatable<TVoxel>
{
public ChunkPos<TChunkSize> ChunkPos { get; set;}
public bool IsLoaded {get;set;}
public VoxelBounds Bounds => new VoxelBounds(Vector3I.Zero,new Vector3I(TChunkSize.Size,TChunkSize.Size,TChunkSize.Size));
private readonly RefPalette<TVoxel> _palette;
private IVoxelChunk<int,TChunkSize> _data;
private readonly SortedDictionary<int,IChunkData> _customData = [];
public WorldChunk(TVoxel @default)
{
_palette = new (@default);
_data = new VoxelChunk<int, TChunkSize>();
}
public Option<T> GetCustomData<T>() where T : IChunkData
{
if (_customData.TryGetValue(ComponentType<T>.Id, out var data))
{
return Option<T>.Some((T)data);
}
return Option<T>.None;
}
public TVoxel GetVoxelAt(VoxelPos<TChunkSize> voxelPos)
{
return _palette.Get(_data.GetVoxel(voxelPos));
}
public void SetCustomData<T>(T value) where T : IChunkData
{
}
public void SetVoxelAt(VoxelPos<TChunkSize> voxelPos, TVoxel voxel)
{
_data.SetVoxel(voxelPos.X,voxelPos.Y,voxelPos.Z,_palette.GetOrAddIndex(voxel));
}
public TVoxel GetVoxel(VoxelPos<TChunkSize> position)=>GetVoxelAt(position);
public TVoxel GetVoxel(VoxelPos voxelPos)=> GetVoxelAt(voxelPos.GetVoxelPos<TChunkSize>());
}
public interface IChunkData
{
ValueTask Serialize(Stream stream);
ValueTask Deserialize(Stream stream);
}
public struct CustomEntry<T>(T data, Action<T,Stream> serialize, Func<Stream, T> deserialize) : IChunkData
{
private T data = data;
private readonly Action<T,Stream> serialize = serialize;
private readonly Func<Stream, T> deserialize = deserialize;
public T GetData()=>data;
public ValueTask Deserialize(Stream stream)
{
data = deserialize(stream);
return ValueTask.CompletedTask;
}
public ValueTask Serialize(Stream stream)
{
serialize(data,stream);
return ValueTask.CompletedTask;
}
}

View File

@@ -0,0 +1 @@
uid://ct81cqcwbb6wk

View File

@@ -0,0 +1,860 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Godot;
using Microsoft.Data.Sqlite;
using SJK.Functional;
using SJK.Voxels;
using SqlKata;
public interface IDataSerializer<T>
{
ValueTask<byte[]> SerializeAsync(T value);
ValueTask<T> DeserializeAsync(Span<byte> bytes);
}
public interface IDataSerializer<T,T2>
{
ValueTask<T2> SerializeAsync(T value);
ValueTask<T> DeserializeAsync(T2 bytes);
}
// public interface IDataStore
// {
// ValueTask<Option<byte[]>> ReadAsync(DataKey path, CancellationToken token = default);
// ValueTask WriteAsync(DataKey path, byte[] data, CancellationToken token = default);
// ValueTask<bool> ExistsAsync(DataKey path, CancellationToken token = default);
// ValueTask DeleteAsync(DataKey path, CancellationToken token = default);
// public bool IsReadOnly { get; }
// }
public interface IDataStore<TDomainKey,T>
{
ValueTask<Option<T>> ReadAsync(TDomainKey key, CancellationToken token = default);
ValueTask WriteAsync(TDomainKey key, T value, CancellationToken token = default);
ValueTask<bool> ExistsAsync(TDomainKey key, CancellationToken token = default);
ValueTask DeleteAsync(TDomainKey key, CancellationToken token = default);
bool IsReadOnly { get; }
}
public interface ISqliteSchema
{
string IntilizeSchema();
(string Table, string Query) GetReadQuery(DataKey key);
(string Table, string Query) GetWriteQuery(DataKey key);
(string Table, string Query) GetExistQuery(DataKey key);
(string Table, string Query) GetRemoveQuery(DataKey key);
void BindKey(SqliteCommand cmd, DataKey key);
void BindData(SqliteCommand cmd, ReadOnlyMemory<byte> data);
}
public interface ISqliteSchema<TKey, TData>
{
string TableName { get; }
void EnsureSchema(SqliteConnection connection);
SqliteCommand CreateSelectCommand(SqliteConnection connection, TKey key);
SqliteCommand CreateInsertCommand(SqliteConnection connection, TKey key, TData data);
SqliteCommand CreateDeleteCommand(SqliteConnection connection, TKey key);
}
public interface ISqliteProvider<TKey, TData>
{
Task<Option<TData>> QueryAsync(SqliteConnection conn, TKey key, CancellationToken token);
Task UpsertAsync(SqliteConnection conn, TKey key, TData data, CancellationToken token);
Task DeleteAsync(SqliteConnection conn, TKey key, CancellationToken token);
}
public sealed class SQLiteDataStore<TKey,TData> : IDataStore<TKey,TData>
{
private readonly SqliteConnection connection;
private readonly ISqliteSchema<TKey,TData> schema;
private readonly ISqliteProvider<TKey,TData> provider;
public SQLiteDataStore(SqliteConnection connection, ISqliteSchema<TKey,TData> schema, ISqliteProvider<TKey,TData> provider, bool isReadOnly)
{
this.connection = connection;
this.schema = schema;
IsReadOnly = isReadOnly;
schema.EnsureSchema(connection);
connection.Open();
}
public bool IsReadOnly { get; }
public async ValueTask DeleteAsync(TKey path, CancellationToken token = default)
{
using var command = schema.CreateDeleteCommand(connection, path);
// var (table, query) = schema.GetRemoveQuery(path);
// using var cmd = connection.CreateCommand();
// cmd.CommandText = query;
// schema.BindKey(cmd, path);
// var results = await cmd.ExecuteScalarAsync(token);
var results = await command.ExecuteScalarAsync(token);
}
public ValueTask<bool> ExistsAsync(TKey path, CancellationToken token = default)
{
throw new NotImplementedException();
// var (table, query) = schema.GetExistQuery(path);
// using var cmd = connection.CreateCommand();
// cmd.CommandText = query;
// schema.BindKey(cmd, path);
// var results = await cmd.ExecuteScalarAsync(token);
// return results is true;
}
public async ValueTask<Option<TData>> ReadAsync(TKey path, CancellationToken token = default)
{
using var command = schema.CreateSelectCommand(connection, path);
// var (table, query) = schema.GetReadQuery(path);
// using var cmd = connection.CreateCommand();
// cmd.CommandText = query;
// schema.BindKey(cmd, path);
// var results = await cmd.ExecuteScalarAsync(token);
var results = await command.ExecuteScalarAsync(token);
return results is TData bytes
? Option<TData>.Some(bytes)
: Option<TData>.None;
}
public Option<TData> Read(TKey path)
{
using var command = schema.CreateSelectCommand(connection, path);
// var (table, query) = schema.GetReadQuery(path);
// using var cmd = connection.CreateCommand();
// cmd.CommandText = query;
// schema.BindKey(cmd, path);
// var results = await cmd.ExecuteScalarAsync(token);
var results = command.ExecuteScalar();
return results is TData bytes
? Option<TData>.Some(bytes)
: Option<TData>.None;
}
public async ValueTask WriteAsync(TKey path, TData data, CancellationToken token = default)
{
using var command = schema.CreateInsertCommand(connection, path,data);
// var (table, query) = schema.GetWriteQuery(path);
// using var cmd = connection.CreateCommand();
// cmd.CommandText = query;
// schema.BindKey(cmd, path);
// schema.BindData(cmd, data);
await command.ExecuteNonQueryAsync(token);
}
}
public sealed class FileDataStore : IDataStore<string,Stream>
{
private readonly string rootDir;
public FileDataStore(string rootDir,bool isReadOnly = false)
{
this.rootDir = rootDir;
IsReadOnly = isReadOnly;
}
public bool IsReadOnly { get; }
public ValueTask DeleteAsync(string path, CancellationToken token = default)
{
if (IsReadOnly) return ValueTask.CompletedTask;
var file = Path.Combine(rootDir, path);// _pathResolver.ResolvePath(path));
if (File.Exists(file))
File.Delete(file);
return ValueTask.CompletedTask;
}
public ValueTask<bool> ExistsAsync(string path, CancellationToken token = default)
{
var file = Path.Combine(rootDir, path);
return ValueTask.FromResult(File.Exists(file));
}
public ValueTask<Option<Stream>> ReadAsync(string path, CancellationToken token = default)
{
// var filePath = rootDir + "/" + path;
// File.Exists(filePath)
var file = Path.Combine(rootDir, path); //_pathResolver.ResolvePath(path));
return ValueTask.FromResult(File.Exists(file) ? Option<Stream>.Some(File.OpenRead(file)):Option<Stream>.None);
// ? Option<byte[]>.Some(await File.ReadAllBytesAsync(file, token))
// : Option<byte[]>.None;
}
public async ValueTask WriteAsync(string path, Stream data, CancellationToken token = default)
{
if (IsReadOnly) return;
var file = Path.Combine(rootDir, path); //_pathResolver.ResolvePath(path));
Directory.CreateDirectory(Path.GetDirectoryName(file)!);
var write = File.OpenWrite(file);
await data.CopyToAsync(write);
}
}
// public sealed class MemoryDataStore : IDataStore
// {
// private readonly System.Collections.Concurrent.ConcurrentDictionary<string, byte[]> _cache = new();
// public bool IsReadOnly => false;
// public ValueTask<bool> ExistsAsync(DataKey path, CancellationToken token = default)
// => new(_cache.ContainsKey(path.ToString()));
// public ValueTask<Option<byte[]>> ReadAsync(DataKey path, CancellationToken token = default)
// {
// GD.PrintS("Read", path, _cache.TryGetValue(path.ToString(), out var data2), data2);
// if (_cache.TryGetValue(path.ToString(), out var data))
// return ValueTask.FromResult(Option<byte[]>.Some(data));
// // if not in cache, try next layer (and cache it)
// // if (_nextLayer is not null)
// // return ReadThroughAsync(path, token);
// return ValueTask.FromResult(Option<byte[]>.None);
// }
// public ValueTask WriteAsync(DataKey path, byte[] data, CancellationToken token = default)
// {
// _cache[path.ToString()] = data;
// GD.PrintS("Write", path, _cache.TryGetValue(path.ToString(), out var data2), data2);
// // Optional: immediate flush to next layer
// // if (_nextLayer is not null)
// // {
// // ms.Position = 0;
// // await _nextLayer.WriteAsync(path, new MemoryStream(_cache[path]), token);
// // }
// return ValueTask.CompletedTask;
// }
// public ValueTask DeleteAsync(DataKey path, CancellationToken token = default)
// {
// _cache.TryRemove(path.ToString(), out _);
// return ValueTask.CompletedTask;
// }
// // public async ValueTask FlushAsync(CancellationToken token = default)
// // {
// // if (_nextLayer is null) return;
// // foreach (var (path, data) in _cache)
// // {
// // using var ms = new MemoryStream(data);
// // await _nextLayer.WriteAsync(path, ms, token);
// // }
// // }
// public void Clear() => _cache.Clear();
// }
// public interface IDataCache<T>
// {
// ValueTask<Option<T>> GetDataAsync(DataKey key);
// ValueTask SetDataAsync(DataKey key, T value, bool dirty = true);
// ValueTask FlushDataAsync(DataKey key);
// ValueTask FlushAllAsync();
// }
// public sealed class MemoryDataLayer<T> : IDataCache<T>
// {
// private readonly IDataStore _storage;
// private readonly IDataSerializer<T> _serializer;
// private readonly ConcurrentDictionary<DataKey, (T data, bool dirty)> _cache = new();
// private readonly bool _autoCache;
// private readonly bool _autoWriteThrough;
// public MemoryDataLayer(IDataStore storage, IDataSerializer<T> serializer, bool autoCache = true, bool autoWriteThrough = false)
// {
// _storage = storage;
// _serializer = serializer;
// _autoCache = autoCache;
// _autoWriteThrough = autoWriteThrough;
// }
// public async ValueTask<Option<T>> GetDataAsync(DataKey key)
// {
// if (_cache.TryGetValue(key, out var entry))
// return Option<T>.Some(entry.data);
// var bytes = await _storage.ReadAsync(key);
// if (!bytes.HasValue)
// return Option<T>.None;
// var chunk = await _serializer.DeserializeAsync(bytes.Value);
// if (_autoCache)
// _cache[key] = (chunk, false);
// return Option<T>.Some(chunk);
// }
// public async ValueTask SetDataAsync(DataKey key, T chunk, bool dirty = true)
// {
// _cache[key] = (chunk, dirty);
// if (_autoWriteThrough && dirty)
// {
// await FlushDataAsync(key, chunk);
// }
// return;
// }
// public async ValueTask FlushDataAsync(DataKey key)
// {
// if (!_cache.TryGetValue(key, out var entry) || !entry.dirty)
// return;
// // var serialized = await _serializer.SerializeAsync(entry.data);
// // await _storage.WriteAsync(key,serialized);
// await FlushDataAsync(key, entry.data);
// _cache[key] = (entry.data, false);
// }
// private async ValueTask FlushDataAsync(DataKey key, T data)
// {
// var serialized = await _serializer.SerializeAsync(data);
// await _storage.WriteAsync(key, serialized);
// }
// public async ValueTask FlushAllAsync()
// {
// foreach (var id in _cache.Keys.ToArray())
// await FlushDataAsync(id);
// }
// }
public interface IFlushableStore<TDomainKey, TData>
{
ValueTask FlushAsync(
IDataStore<TDomainKey, TData>? target,
CancellationToken token = default);
ValueTask FlushAsync(TDomainKey key, IDataStore<TDomainKey, TData>? target, CancellationToken token = default);
}
public interface ILoadedDataStore<TDomainKey, TData>
{
IEnumerable<(TDomainKey, TData)> GetLoadedData();
Option<TData> TryGetData(TDomainKey key);
}
public sealed class MemoryDataLayer<TDomainKey, TData> : IDataStore<TDomainKey, TData>, IFlushableStore<TDomainKey, TData>, ILoadedDataStore<TDomainKey,TData> where TDomainKey : IEquatable<TDomainKey>
{
private readonly ConcurrentDictionary<TDomainKey, (TData data, bool dirty)> _cache = new();
public bool IsReadOnly => false;
public ValueTask<Option<TData>> ReadAsync(TDomainKey key, CancellationToken token = default)=>ValueTask.FromResult(Read(key));
public ValueTask WriteAsync(TDomainKey key, TData value, CancellationToken token = default)
// public ValueTask SetDataAsync(TDomainKey key, TData chunk, bool dirty = true)
{
_cache[key] = (value, true);
return ValueTask.CompletedTask;
}
public async ValueTask FlushAsync(IDataStore<TDomainKey, TData>? target, CancellationToken token = default)
{
if (target is null)
{
return;
}
foreach (var item in _cache)
{
if (item.Value.dirty)
{
await target.WriteAsync(item.Key, item.Value.data, token);
_cache[item.Key] = (item.Value.data, false);
}
}
}
public ValueTask<bool> ExistsAsync(TDomainKey key, CancellationToken token = default) => ValueTask.FromResult(_cache.ContainsKey(key));
public ValueTask DeleteAsync(TDomainKey key, CancellationToken token = default)
{
_cache.TryRemove(key, out var _);
return ValueTask.CompletedTask;
}
public async ValueTask FlushAsync(TDomainKey key, IDataStore<TDomainKey, TData> target, CancellationToken token = default)
{
if (_cache.TryGetValue(key, out var data))
{
await target.WriteAsync(key, data.data, token);
}
}
public IEnumerable<(TDomainKey, TData)> GetLoadedData()
{
return _cache.Select(c=> (c.Key,c.Value.data));
}
public Option<TData> TryGetData(TDomainKey key)
{
return _cache.GetValue(key).Map(f => f.data).ToStructOption();
}
public Option<TData> Read(TDomainKey key)
{
if (_cache.TryGetValue(key, out var entry))
return Option<TData>.Some(entry.data);
return Option<TData>.None;
}
}
public enum LayerPolicy
{
ReadOnly, // used only for reads (terrain gen)
WriteThrough, // always updated when written
WriteBack, // marked dirty and flushed later
Cache // memory only, ephemeral
}
public record DataLayer<TDomainKey, TData>(
IDataStore<TDomainKey, TData> Store,
LayerPolicy Policy,
string Name);
public interface ITwoTierStore<TKey, T> : IDataStore<TKey, T>, IFlushableStore<TKey, T>
{
IDataStore<TKey, T> Cache { get; }
IDataStore<TKey, T> Persistent { get; }
}
public sealed class TwoTierStore<TDomainKey, TData, TCache, TPersistent> : IDataStore<TDomainKey, TData>, IFlushableStore<TDomainKey, TData> where TCache : IDataStore<TDomainKey, TData>, IFlushableStore<TDomainKey, TData> where TPersistent : IDataStore<TDomainKey, TData>
{
public TwoTierStore(TCache cached, TPersistent store)
{
Cached = cached;
Store = store;
}
public TCache Cached { get; }
public TPersistent Store { get; }
public bool IsReadOnly => Cached.IsReadOnly && Store.IsReadOnly;
public async ValueTask DeleteAsync(TDomainKey key, CancellationToken token = default)
{
await Cached.DeleteAsync(key, token);
}
public async ValueTask<bool> ExistsAsync(TDomainKey key, CancellationToken token = default)
{
var exist = await Cached.ExistsAsync(key, token);
if (exist)
{
return exist;
}
return await Store.ExistsAsync(key, token);
}
public async ValueTask<Option<TData>> ReadAsync(TDomainKey key, CancellationToken token = default)
{
var data = await Cached.ReadAsync(key, token);
if (data.HasValue)
{
return data;
}
return await Store.ReadAsync(key, token);
}
public async ValueTask WriteAsync(TDomainKey key, TData value, CancellationToken token = default)
{
await Cached.WriteAsync(key, value, token);
}
public async ValueTask Flush(CancellationToken token = default)
{
await Cached.FlushAsync(Store, token);
}
public ValueTask FlushAsync(IDataStore<TDomainKey, TData>? target, CancellationToken token = default)
{
throw new NotImplementedException();
}
public ValueTask FlushAsync(TDomainKey key, IDataStore<TDomainKey, TData> target, CancellationToken token = default)
{
throw new NotImplementedException();
}
}
public sealed class TwoTierStore<TDomainKey, TData, TCache> : IDataStore<TDomainKey, TData>, IFlushableStore<TDomainKey, TData>, ILoadedDataStore<TDomainKey,TData> where TCache : IDataStore<TDomainKey, TData>, IFlushableStore<TDomainKey, TData> , ILoadedDataStore<TDomainKey,TData>
{
public TwoTierStore(TCache cached, IDataStore<TDomainKey,TData> store)
{
Cached = cached;
Store = store;
}
public TCache Cached { get; }
public IDataStore<TDomainKey,TData> Store { get; }
public bool IsReadOnly => Cached.IsReadOnly && Store.IsReadOnly;
public async ValueTask DeleteAsync(TDomainKey key, CancellationToken token = default)
{
await Cached.DeleteAsync(key, token);
}
public async ValueTask<bool> ExistsAsync(TDomainKey key, CancellationToken token = default)
{
var exist = await Cached.ExistsAsync(key, token);
if (exist)
{
return exist;
}
return await Store.ExistsAsync(key, token);
}
public async ValueTask<Option<TData>> ReadAsync(TDomainKey key, CancellationToken token = default)
{
var data = await Cached.ReadAsync(key, token);
if (data.HasValue)
{
return data;
}
return await Store.ReadAsync(key, token);
}
public async ValueTask WriteAsync(TDomainKey key, TData value, CancellationToken token = default)
{
await Cached.WriteAsync(key, value, token);
}
public async ValueTask Flush(CancellationToken token = default)
{
await Cached.FlushAsync(Store, token);
}
public ValueTask FlushAsync(IDataStore<TDomainKey, TData>? target, CancellationToken token = default)
{
throw new NotImplementedException();
}
public ValueTask FlushAsync(TDomainKey key, IDataStore<TDomainKey, TData> target, CancellationToken token = default)
{
throw new NotImplementedException();
}
public IEnumerable<(TDomainKey, TData)> GetLoadedData()
{
return Cached.GetLoadedData();
}
public Option<TData> TryGetData(TDomainKey key)
{
return Cached.TryGetData(key);
}
public async ValueTask LoadChunkToMemory(TDomainKey key,Func<TDomainKey,TData> factory)
{
var s =await Store.ReadAsync(key);
await Cached.WriteAsync(key,s.Or(()=>factory(key)));
}
}
public sealed class LayeredDataStore<TDomainKey, TData> : IDataStore<TDomainKey, TData>, IFlushableStore<TDomainKey, TData> where TDomainKey : IEquatable<TDomainKey>
{
public bool IsReadOnly => _layers.All(item => item.Policy == LayerPolicy.ReadOnly);
private readonly List<DataLayer<TDomainKey, TData>> _layers;
public LayeredDataStore(IEnumerable<DataLayer<TDomainKey, TData>> dataStores)
{
_layers = dataStores.ToList();
}
public ValueTask DeleteAsync(TDomainKey key, CancellationToken token = default)
{
throw new NotImplementedException();
}
public ValueTask<bool> ExistsAsync(TDomainKey key, CancellationToken token = default)
{
throw new NotImplementedException();
}
public ValueTask FlushAsync(IDataStore<TDomainKey, TData> target, CancellationToken token = default)
{
throw new NotImplementedException();
}
public async ValueTask<Option<TData>> ReadAsync(TDomainKey key, CancellationToken token = default)
{
foreach (var item in _layers)
{
var data = await item.Store.ReadAsync(key, token);
if (data.HasValue)
{
return data;
}
}
return Option<TData>.None;
}
public async ValueTask WriteAsync(TDomainKey key, TData value, CancellationToken token = default)
{
if (_layers[0].Policy == LayerPolicy.ReadOnly || _layers[0].Store.IsReadOnly)
{
throw new Exception();
}
await _layers[0].Store.WriteAsync(key,value, token);
}
public ValueTask FlushAsync(TDomainKey key, IDataStore<TDomainKey, TData> target, CancellationToken token = default)
{
throw new NotImplementedException();
}
}
public readonly record struct DataKey(string Namespace,object Key);
//TODO Replace with ChunkStorage as to sperate inmemory data and stored data
public interface IChunkManger<TVoxel, TChunkSize, TChunk> where TChunkSize : IChunkSize<TChunkSize> where TChunk : IVoxelChunk<TVoxel, TChunkSize>
{
ValueTask<TChunk> GetChunkAsync(ChunkPos<TChunkSize> chunkPos);
ValueTask SetChunkAsync(ChunkPos<TChunkSize> chunkPos, TChunk chunk);
Option<TChunk> TryGetChunk(ChunkPos<TChunkSize> chunkPos);
IEnumerable<(ChunkPos<TChunkSize>,TChunk)> GetLoadedChunks();
}
public sealed class ChunkManger<TVoxel, TChunkSize, TChunkStore,TChunk> :IChunkManger<TVoxel,TChunkSize,TChunk> where TChunkSize : IChunkSize<TChunkSize> where TChunkStore : IDataStore<ChunkPos<TChunkSize>, TChunk>, IFlushableStore<ChunkPos<TChunkSize>, TChunk> , ILoadedDataStore<ChunkPos<TChunkSize>,TChunk> where TChunk : IVoxelChunk<TVoxel,TChunkSize>
{
private TChunkStore chunkStore;
private readonly Func<TChunk> factory;
// public async ValueTask SetChunkAysnc(ChunkPos<TChunkSize> chunkPos, IVoxelChunk<TVoxel, TChunkSize> chunk)
// {
// await chunkStore.WriteAsync(chunkPos, chunk);
// // await cachedData.WriteAsync(chunkPos, chunk);
// }
// public async ValueTask<Option<IVoxelChunk<TVoxel, TChunkSize>>> GetChunkAysnc(ChunkPos<TChunkSize> chunkPos)
// {
// var chunk = await chunkStore.ReadAsync(chunkPos);
// // var chunk = await cachedData.ReadAsync(chunkPos);
// return chunk;
// if (chunk.HasValue)
// {
// return chunk;
// }
// // return await persisitenData.ReadAsync(chunkPos);
// }
public ChunkManger(TChunkStore chunkStore, Func<TChunk> factory)
{
this.chunkStore = chunkStore;
this.factory = factory;
}
// public ChunkManger(MemoryDataLayer<IVoxelChunk<TVoxel, TChunkSize>> memoryDataLayer)
// {
// this.memoryDataLayer = memoryDataLayer;
// }
// private MemoryDataLayer<IVoxelChunk<TVoxel, TChunkSize>> memoryDataLayer;
public async ValueTask SetChunkAsync(ChunkPos<TChunkSize> chunkPos, TChunk voxelChunk)
{
await chunkStore.WriteAsync(chunkPos, voxelChunk);
}
public async ValueTask<TChunk> GetChunkAsync(ChunkPos<TChunkSize> chunkPos)
{
return (await chunkStore.ReadAsync(chunkPos)).Or(factory);
}
public Option<TChunk> TryGetChunk(ChunkPos<TChunkSize> chunkPos)
{
return chunkStore.TryGetData(chunkPos);
}
public IEnumerable<(ChunkPos<TChunkSize>, TChunk)> GetLoadedChunks()
{
return chunkStore.GetLoadedData();
}
// public void SetChunk(ChunkPos<TChunkSize> chunkPos, IVoxelChunk<TVoxel, TChunkSize> voxelChunk)
// {
// Task.Run(() => chunkStore.WriteAsync(chunkPos, voxelChunk)).GetAwaiter().GetResult();
// }
// public Option<IVoxelChunk<TVoxel, TChunkSize>> GetChunk(ChunkPos<TChunkSize> chunkPos)
// {
// return Task.Run(() => chunkStore.ReadAsync(chunkPos)).GetAwaiter().GetResult().Result;
// }
}
public interface IKeyMapper<TDomainKey, TInnerKey>
{
TInnerKey MapKey(TDomainKey key);
}
public class DataStoreMapper<TDomainKey, TData, TInnerKey>(IDataStore<TInnerKey, TData> innerStore, IKeyMapper<TDomainKey, TInnerKey> mapper) : IDataStore<TDomainKey,TData>
{
private readonly IDataStore<TInnerKey, TData> innerStore = innerStore;
private readonly IKeyMapper<TDomainKey, TInnerKey> mapper = mapper;
public bool IsReadOnly => innerStore.IsReadOnly;
public ValueTask DeleteAsync(TDomainKey key, CancellationToken token = default) => innerStore.DeleteAsync(mapper.MapKey(key), token);
public ValueTask<bool> ExistsAsync(TDomainKey key, CancellationToken token = default) => innerStore.ExistsAsync(mapper.MapKey(key), token);
public ValueTask<Option<TData>> ReadAsync(TDomainKey key, CancellationToken token = default) => innerStore.ReadAsync(mapper.MapKey(key), token);
public ValueTask WriteAsync(TDomainKey key, TData value, CancellationToken token = default) => innerStore.WriteAsync(mapper.MapKey(key), value, token);
}
public class DataStoreSerializer<TDomainKey, TData,TInnerData>(IDataStore<TDomainKey, TInnerData> innerStore,IDataSerializer<TData,TInnerData> serializer) : IDataStore<TDomainKey,TData>
{
private readonly IDataStore<TDomainKey, TInnerData> innerStore = innerStore;
private readonly IDataSerializer<TData,TInnerData> serializer = serializer;
public bool IsReadOnly => innerStore.IsReadOnly;
public ValueTask DeleteAsync(TDomainKey key, CancellationToken token = default) => innerStore.DeleteAsync(key, token);
public ValueTask<bool> ExistsAsync(TDomainKey key, CancellationToken token = default) => innerStore.ExistsAsync(key, token);
public async ValueTask<Option<TData>> ReadAsync(TDomainKey key, CancellationToken token = default)
{
var data = await innerStore.ReadAsync(key, token);
return data.HasValue?Option<TData>.Some(await serializer.DeserializeAsync(data.Value)):Option<TData>.None;
}
public async ValueTask WriteAsync(TDomainKey key, TData value, CancellationToken token = default) => await innerStore.WriteAsync(key, await serializer.SerializeAsync(value), token);
}
public sealed class FuncDataMapper<TDomainKey,TNewKey>(Func<TDomainKey, TNewKey> toKey) : IKeyMapper<TDomainKey,TNewKey>
{
private readonly Func<TDomainKey, TNewKey> toKey = toKey;
public TNewKey MapKey(TDomainKey key) => toKey(key);
}
public sealed class FuncDataSerializer<TData, TInnerData>(Func<TInnerData, TData> toData, Func<TData, TInnerData> toBytes) : IDataSerializer<TData, TInnerData>
{
private readonly Func<TInnerData, TData> toData = toData;
private readonly Func<TData, TInnerData> toBytes = toBytes;
public ValueTask<TData> DeserializeAsync(TInnerData bytes) => ValueTask.FromResult(toData(bytes));
public ValueTask<TInnerData> SerializeAsync(TData value) => ValueTask.FromResult(toBytes(value));
}
public class DataRepository<TDomainKey, TData, TInnerKey,TInnerData> : IDataStore<TDomainKey,TData>
{
private readonly IDataStore<TInnerKey,TInnerData> _store;
private readonly IKeyMapper<TDomainKey,TInnerKey> _mapper;
private readonly IDataSerializer<TData,TInnerData> _serializer;
public DataRepository(IDataStore<TInnerKey,TInnerData> store, IKeyMapper<TDomainKey,TInnerKey> mapper, IDataSerializer<TData,TInnerData> serializer, bool isReadOnly = false)
{
_store = store;
_mapper = mapper;
_serializer = serializer;
IsReadOnly = isReadOnly;
}
public bool IsReadOnly { get; }
public ValueTask DeleteAsync(TDomainKey key, CancellationToken token = default)
{
throw new NotImplementedException();
}
public ValueTask<bool> ExistsAsync(TDomainKey key, CancellationToken token = default)
{
throw new NotImplementedException();
}
// public async ValueTask<Option<TData>> GetAsync(TDomainKey key)
// {
// var dataKey = _mapper.ToDataKey(key);
// var bytes = await _store.ReadAsync(dataKey);
// if (!bytes.HasValue) return Option<TData>.None;
// return Option<TData>.Some(await _serializer.DeserializeAsync(bytes.Value));
// }
public async ValueTask<Option<TData>> ReadAsync(TDomainKey key, CancellationToken token = default)
{
var dataKey = _mapper.MapKey(key);
var bytes = await _store.ReadAsync(dataKey,token);
if (!bytes.HasValue) return Option<TData>.None;
return Option<TData>.Some(await _serializer.DeserializeAsync(bytes.Value));
}
// public async ValueTask SetAsync(TDomainKey key, TData data)
// {
// var dataKey = _mapper.ToDataKey(key);
// var bytes = await _serializer.SerializeAsync(data);
// await _store.WriteAsync(dataKey, bytes);
// }
public async ValueTask WriteAsync(TDomainKey key, TData value, CancellationToken token = default)
{
var dataKey = _mapper.MapKey(key);
var bytes = await _serializer.SerializeAsync(value);
await _store.WriteAsync(dataKey, bytes,token);
}
}
public interface IDataCompressor {
byte[] Compress(byte[] bytes);
byte[] DeCompress(byte[] bytes);
}
public sealed class CompressedDataStore<TKey>(IDataStore<TKey,byte[]> inner, IDataCompressor dataCompressor) : IDataStore<TKey,byte[]>
{
private readonly IDataStore<TKey,byte[]> inner = inner;
private readonly IDataCompressor dataCompressor = dataCompressor;
public bool IsReadOnly => inner.IsReadOnly;
public ValueTask DeleteAsync(TKey path, CancellationToken token = default) => inner.DeleteAsync(path, token);
public ValueTask<bool> ExistsAsync(TKey path, CancellationToken token = default) => inner.ExistsAsync(path, token);
public async ValueTask<Option<byte[]>> ReadAsync(TKey path, CancellationToken token = default)
{
var result = await inner.ReadAsync(path, token);
if (!result.HasValue)
return Option<byte[]>.None;
return Option<byte[]>.Some(dataCompressor.DeCompress(result.Value));
}
public async ValueTask WriteAsync(TKey path, byte[] data, CancellationToken token = default)
{
await inner.WriteAsync(path, dataCompressor.Compress(data), token);
}
}public static class TwoTierStoreFactory
{
public static TwoTierStore<TKey, T, MemoryDataLayer<TKey, T>, TStore>
MemoryCached<TKey, T, TStore>(TStore store, LayerPolicy policy = LayerPolicy.WriteBack)
where TStore : IDataStore<TKey, T> where TKey : IEquatable<TKey>
{
var cache = new MemoryDataLayer<TKey, T>();
return new TwoTierStore<TKey, T, MemoryDataLayer<TKey, T>, TStore>(cache, store);
}
public static TwoTierStore<TKey, T, TCache, TStore>
Create<TKey, T, TCache, TStore>(TCache cache, TStore store, LayerPolicy policy = LayerPolicy.WriteBack)
where TCache : IDataStore<TKey, T>, IFlushableStore<TKey, T>
where TStore : IDataStore<TKey, T>
=> new(cache, store);
public static TwoTierStore<TKey, T, MemoryDataLayer<TKey, T>, TStore>
WithMemoryCache<TKey, T, TStore>(
this TStore store,
LayerPolicy policy = LayerPolicy.WriteBack)
where TKey : IEquatable<TKey>
where TStore : IDataStore<TKey, T>
{
var cache = new MemoryDataLayer<TKey, T>();
return new TwoTierStore<TKey, T, MemoryDataLayer<TKey, T>, TStore>(cache, store);
}
public static TwoTierStoreBuilder<TKey, T> For<TKey, T>()
where TKey : IEquatable<TKey>
=> new TwoTierStoreBuilder<TKey, T>();
}
public readonly struct TwoTierStoreBuilder<TKey, T>
where TKey : IEquatable<TKey>
{
public TwoTierStore<TKey, T, MemoryDataLayer<TKey, T>, TStore>
MemoryCached<TStore>(TStore store, LayerPolicy policy = LayerPolicy.WriteBack)
where TStore : IDataStore<TKey, T>
{
var cache = new MemoryDataLayer<TKey, T>();
return new TwoTierStore<TKey, T, MemoryDataLayer<TKey, T>, TStore>(cache, store);
}
public TwoTierStore<TKey, T, MemoryDataLayer<TKey, T>>
MemoryCached(IDataStore<TKey,T> store, LayerPolicy policy = LayerPolicy.WriteBack)
{
var cache = new MemoryDataLayer<TKey, T>();
return new TwoTierStore<TKey, T, MemoryDataLayer<TKey, T>>(cache, store);
}
}

View File

@@ -0,0 +1 @@
uid://c3rdxjvybaq0x

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
uid://dtg4km6ikm0qf

View File

@@ -0,0 +1,30 @@
shader_type spatial;
uniform sampler2D _albedo : source_color;
uniform mat4 cutplane;
varying vec3 wvtx;
void vertex() {
wvtx = (MODEL_MATRIX * vec4(VERTEX,1.0)).xyz;
// Called for every vertex the material is visible on.
}
void fragment() {
vec3 planeNormal = normalize((-cutplane[1].xyz));
float planeDistance = dot(planeNormal , cutplane[3].xyz);
float vertexDistance = dot(planeNormal,wvtx);
float dist = vertexDistance - planeDistance;
if (dist < 0.0)
{
discard;
}
ALBEDO = texture(_albedo, UV).rgb;
// Called for every pixel the material is visible on.
}
//void light() {
// // Called for every pixel for every light affecting the material.
// // Uncomment to replace the default light processing function with this one.
//}

View File

@@ -0,0 +1 @@
uid://b0dpogulmm2p7

View File

@@ -0,0 +1,9 @@
namespace hamsterbyte.DeveloperConsole{
using System;
[AttributeUsage(AttributeTargets.Method)]
public sealed class ConsoleCommandAttribute : Attribute{
public string Prefix = string.Empty;
public string Description = string.Empty;
}
}

View File

@@ -0,0 +1 @@
uid://pducp5spu5en

View File

@@ -0,0 +1,33 @@
using System.Collections.Generic;
using Godot;
namespace hamsterbyte.DeveloperConsole;
public static class DCColorTheme{
public static readonly Dictionary<string, Color> Output = new(){
{ "Number", Colors.White },
{ "Symbol", Colors.White },
{ "Method", Colors.DeepSkyBlue },
{ "Member Variable", Colors.ForestGreen }
};
public static readonly RegionColor[] Regions ={
//Error color
new(){ Color = Colors.DeepPink, Start = "#!", End = "!#" },
//Context Color
new(){ Color = Colors.Goldenrod, Start = "$", End = "::" },
//Comment Color
new(){ Color = Colors.PaleGreen, Start = "/*", End = "*/" }
};
public static readonly Dictionary<string, string> Suggestions = new(){
{ "Method", Colors.DeepSkyBlue.ToHtml() },
{ "Type", Colors.ForestGreen.ToHtml() }
};
}
public struct RegionColor{
public string Start;
public string End;
public Color Color;
}

View File

@@ -0,0 +1 @@
uid://baxk18v2sa2l5

View File

@@ -0,0 +1,237 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Godot;
namespace hamsterbyte.DeveloperConsole;
public static partial class CommandInterpreter{
public static (Node currentNode, Dictionary<string, MethodInfo> commands) Context =>
(_currentNode, _commands);
public static Node CurrentNode => _currentNode;
private static Node _currentNode;
private static string _lastCommand;
public static string LastCommand => _lastCommand;
public static bool Busy{ get; private set; }
#region INITIALIZE
public static void Initialize(){
bool success = TryInitialize();
DC.Print($"Command Interpreter => {success.OKFail()}");
if (success) DCCrosshair.Initialize();
}
private static bool TryInitialize(){
try{
DC.SetSuppressionLevel(DC.PrintSuppression.All);
ChangeContext("/root");
GetStaticCommands(out _staticCommands);
_commands = _staticCommands;
GetInstanceCommands(out _instanceCommands, _currentNode);
DC.SetSuppressionLevel(DC.Instance.PrintSuppression);
return true;
}
catch (Exception e){
e.PrintToDC();
return false;
}
}
#endregion
#region INTERPRET
public static async Task Interpret(string commandString){
try{
if (commandString == "") return;
_lastCommand = commandString;
if (TryShortcuts(commandString)) return;
if (TryMath(commandString)) return;
ConsoleCommand command = ParseConsoleCommand(commandString);
if (command is not null) await ExecuteConsoleCommand(command);
}
catch (Exception e){
HandleDCError(e);
}
finally{
Command.ResetToken();
}
}
private static bool TryShortcuts(string commandString){
for (int i = 0; i < _shortcuts.Length; i++){
if (!commandString.StartsWith(_shortcuts[i].Character)) continue;
_shortcuts[i].Command.Invoke(commandString);
return true;
}
return false;
}
private static readonly char[] _validExpressionStartChars ={ '(', '-' };
private static bool TryMath(string commandString){
if (!char.IsNumber(commandString[0]) && !_validExpressionStartChars.Contains(commandString[0])) return false;
double result = Convert.ToDouble(new DataTable().Compute(commandString, null));
DC.Print(result);
return true;
}
private static async Task ExecuteConsoleCommand(ConsoleCommand command){
CommandReturnType returnType = GetCommandReturnType(command.Method);
if ((int)returnType <= 1){
// If not a task, execute without waiting and print result if it's not void
ExecuteAndPrintResult(command);
}
else{
// If it's a task, execute and await it, then print result if it has one
await ExecuteAndPrintResultAsync(command, returnType);
}
}
private static void ExecuteAndPrintResult(ConsoleCommand command){
Type returnType = command.Method.ReturnType;
dynamic result = command.Method.Invoke(_currentNode, command.Params);
if (returnType != typeof(void)){
DC.Print(result);
}
}
private static async Task ExecuteAndPrintResultAsync(ConsoleCommand command, CommandReturnType returnType){
DisplayServer.VSyncMode tVsync = DisplayServer.WindowGetVsyncMode();
DisplayServer.WindowSetVsyncMode(0);
dynamic task = command.Method.Invoke(_currentNode, command.Params);
if (task is not null){
DC.Print("Processing...");
await task;
dynamic result = task.GetType().GetProperty("Result")?.GetValue(task, null);
if (result is not null && returnType == CommandReturnType.TaskWithResult){
DC.Print(result);
}
}
else{
throw new DCInvalidCommandException(command.Method.Name);
}
DisplayServer.WindowSetVsyncMode(tVsync);
}
private static ConsoleCommand ParseConsoleCommand(string commandString){
return ConsoleCommand.FromString(commandString, _commands);
}
private static void HandleDCError(Exception e){
while (e.InnerException is not null){
e = e.InnerException;
}
e.PrintToDC();
}
#endregion
#region COMMANDS
private static Dictionary<string, MethodInfo> _commands = new();
public static Dictionary<string, MethodInfo> Commands => _commands;
public static DCDelegates.onConsoleCommandsUpdated OnGetStaticCommands;
private static Dictionary<string, MethodInfo> _instanceCommands = new();
public static Dictionary<string, MethodInfo> InstanceCommands => _instanceCommands;
public static DCDelegates.onConsoleCommandsUpdated OnGetInstanceCommands;
private static Dictionary<string, MethodInfo> _staticCommands = new();
public static Dictionary<string, MethodInfo> StaticCommands => _staticCommands;
private static void GetStaticCommands(out Dictionary<string, MethodInfo> commands){
commands = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(assembly => assembly.GetTypes())
.SelectMany(type => type.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static)
.Where(method => method.GetCustomAttributes(typeof(ConsoleCommandAttribute), true).Any()))
.OrderBy(method => {
ConsoleCommandAttribute commandAttribute = method.GetCustomAttribute<ConsoleCommandAttribute>();
return commandAttribute?.Prefix ?? string.Empty;
})
.ThenBy(method => method.Name)
.ToDictionary(method => {
ConsoleCommandAttribute commandAttribute = method.GetCustomAttribute<ConsoleCommandAttribute>();
string prefix = commandAttribute?.Prefix != string.Empty
? commandAttribute?.Prefix + '.'
: string.Empty;
return $"{prefix}{method.Name}";
});
OnGetStaticCommands?.Invoke(commands);
}
private static void GetInstanceCommands(out Dictionary<string, MethodInfo> commands, object targetObject){
commands = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(assembly => assembly.GetTypes()
.Where(type => type == targetObject.GetType()))
.SelectMany(type => type.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)
.Where(method => method.GetCustomAttributes(typeof(ConsoleCommandAttribute), true).Any()))
.OrderBy(method => method.Name)
.ToDictionary(method => {
ConsoleCommandAttribute commandAttribute = method.GetCustomAttribute<ConsoleCommandAttribute>();
string prefix = commandAttribute?.Prefix != string.Empty
? commandAttribute?.Prefix + '.'
: string.Empty;
return $"{prefix}{targetObject.GetType().BaselessString()}.{method.Name}";
});
OnGetInstanceCommands?.Invoke(commands);
}
private static void ResetContextCommands(){
foreach (string key in _instanceCommands.Keys){
_commands.Remove(key);
}
_instanceCommands.Clear();
}
private static void MergeConsoleCommands(){
foreach (KeyValuePair<string, MethodInfo> commandPair in _instanceCommands){
_commands.TryAdd(commandPair.Key, commandPair.Value);
}
OnConsoleCommandsUpdated?.Invoke(_commands);
}
public static DCDelegates.onConsoleCommandsUpdated OnConsoleCommandsUpdated;
private static void UpdateContextCommands(Node selectedNode){
ResetContextCommands();
GetInstanceCommands(out _instanceCommands, selectedNode);
MergeConsoleCommands();
}
#endregion
private static CommandReturnType GetCommandReturnType(MethodInfo methodInfo){
Type returnType = methodInfo.ReturnType;
if (returnType == typeof(void)) return CommandReturnType.None;
if (returnType == typeof(Task)) return CommandReturnType.Task;
if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(Task<>))
return CommandReturnType.TaskWithResult;
return CommandReturnType.Type;
}
private enum CommandReturnType{
None,
Type,
Task,
TaskWithResult
}
}

View File

@@ -0,0 +1 @@
uid://cfu0pu0m1m22j

View File

@@ -0,0 +1,46 @@
using System;
using Godot;
namespace hamsterbyte.DeveloperConsole;
public partial class CommandInterpreter{
public static DCDelegates.onContextChanged OnContextChanged;
public static void ChangeContext(string path){
Node selectedNode;
if (path.StartsWith("/.")){
ContextLevelUp(path.TrimStart('/').AsSpan(), out selectedNode);
}
else{
ContextFromPath(path, out selectedNode);
}
if (selectedNode is null) throw new DCException($"'{path}' does not exist.");
UpdateContextCommands(selectedNode);
_currentNode = selectedNode;
OnContextChanged?.Invoke(Context);
}
private static void ContextLevelUp(ReadOnlySpan<char> trailingSpan, out Node selectedNode){
selectedNode = _currentNode;
for (int i = 0; i < trailingSpan.Length; i++){
if (selectedNode is null || trailingSpan[i] != '.') break;
Node parent = selectedNode.GetParentOrNull<Node>();
if (parent is not null){
selectedNode = parent;
}
else{
break;
}
}
}
private static void ContextFromPath(string path, out Node selectedNode){
selectedNode = DC.Instance.GetNodeOrNull(
path.StartsWith("/root")
? path //path is absolute
: $"{DC.CurrentNode.GetPath()}{path}" //path is relative
);
}
}

View File

@@ -0,0 +1 @@
uid://bk1byj8vgtc4e

View File

@@ -0,0 +1,49 @@
using System.Collections.Generic;
using System.Reflection;
using System.Text;
namespace hamsterbyte.DeveloperConsole;
public partial class CommandInterpreter{
private static void GetHelp(string commandString){
if (commandString.Length < 2){
Help(_commands);
}
else{
if (commandString[1] == '?'){
Help(_instanceCommands);
}
else{
Help(_commands, commandString.TrimStart('?'));
}
}
}
private static void Help(Dictionary<string, MethodInfo> commands, string searchString = ""){
foreach (KeyValuePair<string, MethodInfo> c in commands){
string description = c.Value.GetCustomAttribute<ConsoleCommandAttribute>()?.Description;
if (!c.Key.ToLower().Contains(searchString.ToLower()) && !description!.ToLower().Contains(searchString.ToLower())) continue;
int line = DC.Instance.LastOutputLineIndex;
ParameterInfo[] infos = c.Value.GetParameters();
StringBuilder b = new();
b.Append($"{c.Key}(");
for (int i = 0; i < infos.Length; i++){
b.Append($"{(i == 0 ? string.Empty : ", ")}{infos[i].ParameterType.BaselessString()} {infos[i].Name}");
}
b.Append(')');
string formattedString = b.ToString();
foreach (KeyValuePair<string, string> kvp in DCExtensions.TypeReplacementStrings){
string replace = formattedString.Replace(kvp.Key, kvp.Value);
formattedString = replace;
}
DC.Print(formattedString);
if (description == string.Empty) continue;
DC.IncreaseIndent();
DC.Print($"/* {description} */");
DC.DecreaseIndent();
DC.Instance.CallDeferred(nameof(DC.Instance.FoldLine), line);
}
}
}

View File

@@ -0,0 +1 @@
uid://uajav2gns4to

View File

@@ -0,0 +1,10 @@
using System;
namespace hamsterbyte.DeveloperConsole;
public partial class CommandInterpreter{
private static readonly (char Character, Action<string> Command)[] _shortcuts ={
new('/', ChangeContext),
new('?', GetHelp)
};
}

View File

@@ -0,0 +1 @@
uid://xdjd61gnvv5g

View File

@@ -0,0 +1,11 @@
namespace hamsterbyte.DeveloperConsole;
public partial class DC{
/// <summary>
/// Quit the game
/// </summary>
[ConsoleCommand(Prefix = "Application", Description = "Quit the game")]
private static void Quit(){
Instance.GetTree().Quit();
}
}

View File

@@ -0,0 +1 @@
uid://cjkfon8d3lvm4

View File

@@ -0,0 +1,24 @@
using System.Threading;
namespace hamsterbyte.DeveloperConsole;
public static class Command {
private static CancellationTokenSource _cancellationTokenSource = new();
public static CancellationToken Token => _cancellationTokenSource.Token;
public static DCDelegates.onCallback OnCancel;
[ConsoleCommand(Prefix = "Command",
Description = "Cancel all running console commands that use the command token.")]
private static void Cancel(){
_cancellationTokenSource.Cancel();
OnCancel?.InvokeAwaited();
}
public static void ResetToken(){
_cancellationTokenSource.Dispose();
_cancellationTokenSource = new CancellationTokenSource();
}
}

View File

@@ -0,0 +1 @@
uid://bqf0fhngjcpf

View File

@@ -0,0 +1,11 @@
namespace hamsterbyte.DeveloperConsole;
public static partial class OutputCommands{
/// <summary>
/// Clear the output console
/// </summary>
[ConsoleCommand(Prefix = "Output", Description = "Clear all output and command history from the console")]
private static void Clear(){
DC.OnClear?.Invoke();
}
}

View File

@@ -0,0 +1 @@
uid://bp8n8x32b1x5w

View File

@@ -0,0 +1,88 @@
using System;
using System.Collections.Generic;
using System.Reflection;
namespace hamsterbyte.DeveloperConsole;
public class ConsoleCommand{
public MethodInfo Method;
public object[] Params;
public override string ToString() => $"{Method.Name} ({Params?.Length ?? 0})";
private ConsoleCommand(MethodInfo method, object[] parameters){
Method = method;
Params = parameters;
}
public static ConsoleCommand FromString(string commandString, Dictionary<string, MethodInfo> commands){
if (string.IsNullOrEmpty(commandString)){
throw new DCException("Cannot parse an empty command string.");
}
string[] decomposedCommand = DecomposeCommandString(commandString);
if (!commands.TryGetValue(decomposedCommand[0], out MethodInfo method)){
throw new DCInvalidCommandException(decomposedCommand[0]);
}
ParameterInfo[] commandParameterInfos = method.GetParameters();
ValidateParameterCount(commandParameterInfos.Length, decomposedCommand.Length - 1, method.Name);
object[] parameters = ParseParameters(decomposedCommand, commandParameterInfos);
return new ConsoleCommand(method, parameters);
}
private static string[] DecomposeCommandString(string commandString){
return commandString.Split(new[]{ '(', ',', ')' }, StringSplitOptions.RemoveEmptyEntries);
}
private static void ValidateParameterCount(int expectedCount, int actualCount, string methodName){
if (expectedCount != actualCount){
throw new DCParameterMismatchException(methodName, expectedCount);
}
}
private static object[] ParseParameters(string[] decomposedCommand, ParameterInfo[] commandParameterInfos){
object[] parameters = new object[commandParameterInfos.Length];
for (int i = 1; i < decomposedCommand.Length; i++){
Type parameterType = commandParameterInfos[i - 1].ParameterType;
string parameterString = decomposedCommand[i].Trim();
if (TryParseParameter(parameterType, parameterString, out object parsedValue)){
parameters[i - 1] = parsedValue;
}
else{
throw new DCInvalidParameterFormatException(parameterString, parameterType);
}
}
return parameters;
}
private static bool TryParseParameter(Type parameterType, string parameterString, out object parsedValue){
if (parameterType == typeof(string)){
parsedValue = parameterString;
return true;
}
MethodInfo parseMethod = parameterType.GetMethod("Parse", new[]{ typeof(string) });
if (parseMethod is not null){
try{
parsedValue = parseMethod.Invoke(null, new object[]{ parameterString });
return true;
}
catch{
throw new DCParseFailureException(parameterType);
}
}
parsedValue = null;
return false;
}
}

View File

@@ -0,0 +1 @@
uid://lsnwo7ujgwf0

View File

@@ -0,0 +1,121 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Godot;
namespace hamsterbyte.DeveloperConsole{
public partial class DC{
/// <summary>
/// Print paths to immediate children of context
/// </summary>
[ConsoleCommand(Prefix = "Context", Description = "Print a list of immediate children in the current context")]
private static void GetChildren(){
if (CurrentNode.GetChildCount() > 0){
foreach (Node node in CurrentNode.GetChildren()){
if (node.Name == "DeveloperConsole") continue;
Print(node.GetPath());
}
}
else{
throw new DCException("Context contains no children.");
}
}
/// <summary>
/// Print all paths to all children in context, includes internal
/// </summary>
[ConsoleCommand(Prefix = "Context",
Description = "Print a list of all children in the current context. Includes internal")]
private static void GetAllChildren(){
if (CurrentNode.GetChildCount() > 0){
GetChildren(CurrentNode);
}
else{
throw new DCException("Context contains no children");
}
}
/// <summary>
/// Print all node paths in the scene tree
/// </summary>
[ConsoleCommand(Prefix = "Context", Description = "Print a list of all nodes in the tree")]
private static void GetTree(){
if (Instance.GetTree().Root.GetChildCount() != 0){
GetChildren(Instance.GetTree().Root);
}
else{
throw new DCException("Tree contains no children");
}
}
private static void GetChildren(Node targetNode){
if (targetNode.GetChildCount() <= 0) return;
foreach (Node node in targetNode.GetChildren()){
if (node.GetPath().ToString()!.Contains("DeveloperConsole")) continue;
Print(node.GetPath());
IncreaseIndent();
GetChildren(node);
DecreaseIndent();
}
}
/// <summary>
/// Recursively search children of the current node for a child with a given name
/// If found, set current node to search result
/// </summary>
/// <param name="name">Name of the node. Case Sensitive</param>
[ConsoleCommand(Prefix = "Context", Description =
"Search recursively by name through children in the current context. Print all found nodes to console.")]
private static void FindChild(string name){
if (FindIn(name, CurrentNode).Count == 0)
throw new DCException($"Node with name '{name}' does not exist in the current context.");
}
/// <summary>
/// Recursively search entire scene tree for a node with a given name
/// If found, set current node to search result
/// Prefer using FindChild for performance
/// </summary>
/// <param name="name">Name of the node. Case Sensitive</param>
[ConsoleCommand(Prefix = "Context", Description =
"Search recursively by name through all nodes in scene tree. Print all found nodes to console")]
private static void Find(string name){
if (FindIn(name, Instance.GetTree().Root).Count == 0)
throw new DCException($"Node with name '{name}' does not exist in the scene tree.");
}
private static List<string> FindIn(string name, Node context){
Queue<Node> _treeQueue = new();
List<string> foundNodes = new();
Node node;
for (int i = 0; i < context.GetChildren().Count; i++){
node = context.GetChildren()[i];
if (node.Name == "DeveloperConsole") continue;
_treeQueue.Enqueue(node);
}
while (_treeQueue.Count > 0){
node = _treeQueue.Dequeue();
if (node.Name.ToString()!.ToLower().Contains(name.ToLower())){
foundNodes.Add(node.GetPath());
}
foreach (Node child in node.GetChildren()){
_treeQueue.Enqueue(child);
}
}
if (foundNodes.Count <= 0) return foundNodes;
{
for (int i = 0; i < foundNodes.Count; i++){
Print($"{foundNodes[i]}");
}
if (foundNodes.Count == 1){
ChangeContext(foundNodes[0]);
}
}
return foundNodes;
}
}
}

View File

@@ -0,0 +1 @@
uid://dfl1b0g22wbck

View File

@@ -0,0 +1,29 @@
using System;
using Godot;
namespace hamsterbyte.DeveloperConsole;
public partial class Display{
[ConsoleCommand(Prefix = "Display", Description = "Set Vsync mode. Use GetVsyncModes() for a list of valid options. Method expects int")]
private static void SetVsyncMode(int mode){
DisplayServer.WindowSetVsyncMode((DisplayServer.VSyncMode)mode);
}
[ConsoleCommand(Prefix = "Display", Description = "Print the current Vsync mode to the console")]
private static void GetCurrentVsyncMode(){
DC.Print((int)DisplayServer.WindowGetVsyncMode());
}
[ConsoleCommand(Prefix = "Display", Description = "Print a list of all valid vsync mode indices and names")]
private static void GetVsyncModes(){
string[] modes = Enum.GetNames(typeof(DisplayServer.VSyncMode));
for (int i = 0; i < modes.Length; i++){
DC.Print($"{i} - {modes[i]}");
}
}
[ConsoleCommand(Prefix = "Display", Description = "Print the current mouse position")]
private static void GetMousePosition(){
DC.Print(DC.Root.GetViewport().GetMousePosition());
}
}

View File

@@ -0,0 +1 @@
uid://iavcf5e0axvd

View File

@@ -0,0 +1,77 @@
using System.Threading.Tasks;
using Godot;
namespace hamsterbyte.DeveloperConsole;
public static partial class DCEngine{
#region GET
[ConsoleCommand(Prefix = "Engine", Description = "Print current fps")]
private static double GetFPS() => Engine.GetFramesPerSecond();
[ConsoleCommand(Prefix = "Engine", Description = "Print number of frames drawn since init")]
private static int GetFramesDrawn() => Engine.GetFramesDrawn();
[ConsoleCommand(Prefix = "Engine", Description = "Print number of physics frames since init")]
private static ulong GetPhysicsFrames() => Engine.GetPhysicsFrames();
[ConsoleCommand(Prefix = "Engine", Description = "Print number of process frames since init")]
private static ulong GetProcessFrames() => Engine.GetProcessFrames();
[ConsoleCommand(Prefix = "Engine", Description = "Print current engine time scale")]
private static double GetTimeScale() => Engine.TimeScale;
[ConsoleCommand(Prefix = "Engine", Description = "Print max fps")]
private static int GetMaxFPS() => Engine.MaxFps;
[ConsoleCommand(Prefix = "Engine", Description = "Print physics jitter fix")]
private static double GetPhysicsJitterFix() => Engine.PhysicsJitterFix;
[ConsoleCommand(Prefix = "Engine", Description = "Print physics ticks per second")]
private static int GetPhysicsTicksPerSecond() => Engine.PhysicsTicksPerSecond;
[ConsoleCommand(Prefix = "Engine", Description = "Print max physics steps per frame")]
private static int GetMaxPhysicsStepsPerFrame() => Engine.MaxPhysicsStepsPerFrame;
#endregion
#region SET
[ConsoleCommand(Prefix = "Engine", Description = "Set the current engine time scale")]
private static double SetTimeScale(double timeScale){
Engine.TimeScale = timeScale;
return GetTimeScale();
}
[ConsoleCommand(Prefix = "Engine", Description = "Set the physics jitter fix")]
private static double SetPhysicsJitterFix(double physicsJitterFix){
Engine.PhysicsJitterFix = physicsJitterFix;
return GetPhysicsJitterFix();
}
[ConsoleCommand(Prefix = "Engine", Description = "Suppress errors from being output to Godot console")]
private static bool SuppressGDErrorMessages(bool suppress){
Engine.PrintErrorMessages = !suppress;
return !Engine.PrintErrorMessages;
}
[ConsoleCommand(Prefix = "Engine", Description = "Set physics ticks per second")]
private static int SetPhysicsTicksPerSecond(int ticks){
Engine.PhysicsTicksPerSecond = ticks;
return GetPhysicsTicksPerSecond();
}
[ConsoleCommand(Prefix = "Engine", Description = "Set max physics steps per frame")]
private static int SetMaxPhysicsStepsPerFrame(int maxSteps){
Engine.MaxPhysicsStepsPerFrame = maxSteps;
return GetMaxPhysicsStepsPerFrame();
}
[ConsoleCommand(Prefix = "Engine", Description = "Set max fps, a value of 0 means unlimited")]
private static int SetMaxFPS(int maxFPS){
Engine.MaxFps = maxFPS;
return GetMaxFPS();
}
#endregion
}

View File

@@ -0,0 +1 @@
uid://dnq1yjcsndmtg

View File

@@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
using Godot;
namespace hamsterbyte.DeveloperConsole;
public static class Level{
public static DCDelegates.onCallback OnSceneLoaded;
private static List<ResourceInformation> _sceneInfos = new();
// ReSharper disable once MemberCanBePrivate.Global
public static List<ResourceInformation> SceneInfos{
get{
if (_sceneInfos.Count == 0){
InitializeScenes();
}
return _sceneInfos;
}
}
private static void InitializeScenes(){
DC.Print("Initializing Levels List...");
Resources.GetResourceInformation(ResourcePaths.Levels, ref _sceneInfos);
}
[ConsoleCommand(Prefix = "Level", Description = "Print a table of all scenes in the Levels folder")]
private static void GetAll(){
for (int i = 0; i < SceneInfos.Count; i++){
DC.Print($"ID:: {i.ToString().SetWidth(20)}{SceneInfos[i]}");
}
}
[ConsoleCommand(Prefix = "Level",
Description =
"Load a level by index, overwriting the current context. Level.GetAll() to print a table of all levels.")]
private static void Load(int index){
if (DC.Mode == 1) throw new DCException("Cannot load levels from Crosshair Mode, switch to terminal.");
try{
PackedScene p = ResourceLoader.Load<PackedScene>(SceneInfos[index].Path);
foreach (Node n in DC.CurrentNode.GetChildren()){
n.QueueFree();
}
DC.CurrentNode.ReplaceBy(p.Instantiate());
DC.CurrentNode.QueueFree();
OnSceneLoaded?.InvokeAwaited();
}
catch (Exception e){
throw new DCException($"Failed to load level {e.Message}");
}
}
[ConsoleCommand(Prefix = "Level",
Description = "Load a level by index into the current context. Level.GetAll() to print a table of all levels.")]
private static void LoadAdditive(int index){
if (DC.Mode == 1) throw new DCException("Cannot load levels from Crosshair Mode, switch to terminal.");
try{
PackedScene p = ResourceLoader.Load<PackedScene>(SceneInfos[index].Path);
DC.CurrentNode.AddChild(p.Instantiate());
OnSceneLoaded?.InvokeAwaited();
}
catch (Exception e){
throw new DCException($"Failed to load level {e.Message}");
}
}
}

View File

@@ -0,0 +1 @@
uid://b2amvjqvxb3gn

View File

@@ -0,0 +1,71 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace hamsterbyte.DeveloperConsole;
public static class MathCommands{
private const double DegToRad = Math.PI / 180;
private const double RadToDeg = 180 / Math.PI;
[ConsoleCommand(Prefix = "Math", Description = "Print value of x raised to the power of y")]
private static double Pow(double x, double y) => Math.Pow(x, y);
[ConsoleCommand(Prefix = "Math", Description = "Print the square root of a value")]
private static double Sqrt(double val) => Math.Sqrt(val);
[ConsoleCommand(Prefix = "Math", Description = "Print sine of angle from degrees")]
private static double Sin(double value) => Math.Round(Math.Sin(value * DegToRad), 3);
[ConsoleCommand(Prefix = "Math", Description = "Print arc sine in degrees")]
private static double Asin(double value) => Math.Round(Math.Asin(value) * RadToDeg, 3);
[ConsoleCommand(Prefix = "Math", Description = "Print cosine of angle from degrees")]
private static double Cos(double value) => Math.Round(Math.Cos(value * DegToRad), 3);
[ConsoleCommand(Prefix = "Math", Description = "Print arc cosine in degrees")]
private static double Acos(double value) => Math.Round(Math.Acos(value) * RadToDeg, 3);
[ConsoleCommand(Prefix = "Math", Description = "Print tangent of angle from degrees")]
private static double Tan(double value) => Math.Round(Math.Tan(value * DegToRad), 3);
[ConsoleCommand(Prefix = "Math", Description = "Print arc tangent in degrees")]
private static double Atan(double value) => Math.Round(Math.Atan(value) * RadToDeg, 3);
[ConsoleCommand(Prefix = "Math", Description = "Print angle whose tangent is a quotient of two specified numbers")]
private static double Atan2(double x, double y) => Math.Round(Math.Atan2(x, y), 3);
[ConsoleCommand(Prefix = "Math", Description = "Check if a number is prime")]
private static Task<bool> IsPrime(long number){
CancellationToken ct = Command.Token;
return Task.Run(() => {
ct.ThrowIfCancellationRequested();
if (number == 2) return true;
if (number < 2 || number % 2 == 0) return false;
int squareRoot = (int)Math.Sqrt(number);
for (long i = 3; i <= squareRoot; i += 2){
ct.ThrowIfCancellationRequested();
if (number % i == 0) return false;
}
return true;
}, ct
);
}
[ConsoleCommand(Prefix = "Math",
Description = "Print a list of all prime numbers from start to end. Start is always greater than 2.")]
private static async Task ListPrimes(long start, long end){
CancellationToken ct = Command.Token;
start = start < 2 ? 2 : start;
DC.ShowProgress((int)(end - start));
for (long i = start; i <= end; i++){
ct.ThrowIfCancellationRequested();
if (await IsPrime(i)){
DC.Print(i);
}
DC.IncrementProgress(1);
}
}
}

Some files were not shown because too many files have changed in this diff Show More