test
This commit is contained in:
@@ -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
|
|
||||||
Submodule src/SjkScripts updated: 1fa265a1c3...3af30d3447
1005
src/voxelgame/ChunkSurfaceNetTest.cs
Normal file
1005
src/voxelgame/ChunkSurfaceNetTest.cs
Normal file
File diff suppressed because it is too large
Load Diff
1
src/voxelgame/ChunkSurfaceNetTest.cs.uid
Normal file
1
src/voxelgame/ChunkSurfaceNetTest.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://q2phyhqtiacu
|
||||||
45
src/voxelgame/FloodFill/VoxelFloodFill.cs
Normal file
45
src/voxelgame/FloodFill/VoxelFloodFill.cs
Normal 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));
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
1
src/voxelgame/FloodFill/VoxelFloodFill.cs.uid
Normal file
1
src/voxelgame/FloodFill/VoxelFloodFill.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://bb46scw4c0yhh
|
||||||
@@ -5,4 +5,8 @@ using System;
|
|||||||
public partial class NewScript : Node
|
public partial class NewScript : Node
|
||||||
{
|
{
|
||||||
IOption<int> OptionTest;
|
IOption<int> OptionTest;
|
||||||
|
void test()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
194
src/voxelgame/SimpleDataBase.cs
Normal file
194
src/voxelgame/SimpleDataBase.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
1
src/voxelgame/SimpleDataBase.cs.uid
Normal file
1
src/voxelgame/SimpleDataBase.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://duss0kqmyo2ee
|
||||||
@@ -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>
|
||||||
19
src/voxelgame/VoxelGame.csproj.old
Normal file
19
src/voxelgame/VoxelGame.csproj.old
Normal 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
102
src/voxelgame/VoxelGame.sln
Normal 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
|
||||||
142
src/voxelgame/Voxels/BoxelMesher.cs
Normal file
142
src/voxelgame/Voxels/BoxelMesher.cs
Normal 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
|
||||||
|
};
|
||||||
|
}
|
||||||
1
src/voxelgame/Voxels/BoxelMesher.cs.uid
Normal file
1
src/voxelgame/Voxels/BoxelMesher.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://puaveig1esds
|
||||||
464
src/voxelgame/Voxels/Chunks/ChunkPos.cs
Normal file
464
src/voxelgame/Voxels/Chunks/ChunkPos.cs
Normal 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 you’re 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);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
1
src/voxelgame/Voxels/Chunks/ChunkPos.cs.uid
Normal file
1
src/voxelgame/Voxels/Chunks/ChunkPos.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://dlgeisueoqh40
|
||||||
193
src/voxelgame/Voxels/Chunks/IVoxelChunk.cs
Normal file
193
src/voxelgame/Voxels/Chunks/IVoxelChunk.cs
Normal 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;
|
||||||
|
}
|
||||||
1
src/voxelgame/Voxels/Chunks/IVoxelChunk.cs.uid
Normal file
1
src/voxelgame/Voxels/Chunks/IVoxelChunk.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://bn4dkifwcftqm
|
||||||
316
src/voxelgame/Voxels/Chunks/Mesher/ChunkMesher.cs
Normal file
316
src/voxelgame/Voxels/Chunks/Mesher/ChunkMesher.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1
src/voxelgame/Voxels/Chunks/Mesher/ChunkMesher.cs.uid
Normal file
1
src/voxelgame/Voxels/Chunks/Mesher/ChunkMesher.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://bd586qflhhi13
|
||||||
1071
src/voxelgame/Voxels/Chunks/Mesher/GreedyMesher.cs
Normal file
1071
src/voxelgame/Voxels/Chunks/Mesher/GreedyMesher.cs
Normal file
File diff suppressed because it is too large
Load Diff
1
src/voxelgame/Voxels/Chunks/Mesher/GreedyMesher.cs.uid
Normal file
1
src/voxelgame/Voxels/Chunks/Mesher/GreedyMesher.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://b13215rwdt03o
|
||||||
1
src/voxelgame/Voxels/Chunks/OctTree.cs.uid
Normal file
1
src/voxelgame/Voxels/Chunks/OctTree.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://dh21dhxsoadt4
|
||||||
224
src/voxelgame/Voxels/Chunks/Octree.cs
Normal file
224
src/voxelgame/Voxels/Chunks/Octree.cs
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1
src/voxelgame/Voxels/Chunks/Octree.cs.uid
Normal file
1
src/voxelgame/Voxels/Chunks/Octree.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://ctjgia5dlt8xf
|
||||||
58
src/voxelgame/Voxels/Chunks/SparseVoxelChunk.cs
Normal file
58
src/voxelgame/Voxels/Chunks/SparseVoxelChunk.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
1
src/voxelgame/Voxels/Chunks/SparseVoxelChunk.cs.uid
Normal file
1
src/voxelgame/Voxels/Chunks/SparseVoxelChunk.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://btrlde2p7lphe
|
||||||
384
src/voxelgame/Voxels/Chunks/SurfaceNet.cs
Normal file
384
src/voxelgame/Voxels/Chunks/SurfaceNet.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
1
src/voxelgame/Voxels/Chunks/SurfaceNet.cs.uid
Normal file
1
src/voxelgame/Voxels/Chunks/SurfaceNet.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://cmmco4k75cl77
|
||||||
409
src/voxelgame/Voxels/Chunks/SurfaceNetMesher.cs
Normal file
409
src/voxelgame/Voxels/Chunks/SurfaceNetMesher.cs
Normal 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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
1
src/voxelgame/Voxels/Chunks/SurfaceNetMesher.cs.uid
Normal file
1
src/voxelgame/Voxels/Chunks/SurfaceNetMesher.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://bn37xdn3uyrdx
|
||||||
106
src/voxelgame/Voxels/Chunks/VoxelChunk.cs
Normal file
106
src/voxelgame/Voxels/Chunks/VoxelChunk.cs
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
1
src/voxelgame/Voxels/Chunks/VoxelChunk.cs.uid
Normal file
1
src/voxelgame/Voxels/Chunks/VoxelChunk.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://dsvwlpnv2gyrv
|
||||||
99
src/voxelgame/Voxels/Chunks/VoxelChunkEnumerator.cs
Normal file
99
src/voxelgame/Voxels/Chunks/VoxelChunkEnumerator.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
1
src/voxelgame/Voxels/Chunks/VoxelChunkEnumerator.cs.uid
Normal file
1
src/voxelgame/Voxels/Chunks/VoxelChunkEnumerator.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://cd5ybj683y0be
|
||||||
202
src/voxelgame/Voxels/Chunks/VoxelSlice.cs
Normal file
202
src/voxelgame/Voxels/Chunks/VoxelSlice.cs
Normal 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, it’s 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,
|
||||||
|
}
|
||||||
1
src/voxelgame/Voxels/Chunks/VoxelSlice.cs.uid
Normal file
1
src/voxelgame/Voxels/Chunks/VoxelSlice.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://b2rcodqw5v2ku
|
||||||
136
src/voxelgame/Voxels/ESCThing/StructuralInstance.Builder.cs
Normal file
136
src/voxelgame/Voxels/ESCThing/StructuralInstance.Builder.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// }
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://bk721rnhegl0x
|
||||||
258
src/voxelgame/Voxels/ESCThing/StructuralInstance.cs
Normal file
258
src/voxelgame/Voxels/ESCThing/StructuralInstance.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
1
src/voxelgame/Voxels/ESCThing/StructuralInstance.cs.uid
Normal file
1
src/voxelgame/Voxels/ESCThing/StructuralInstance.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://cexmjk01dgtbe
|
||||||
66
src/voxelgame/Voxels/ESCThing/TestStructural.cs
Normal file
66
src/voxelgame/Voxels/ESCThing/TestStructural.cs
Normal 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;
|
||||||
|
}
|
||||||
1
src/voxelgame/Voxels/ESCThing/TestStructural.cs.uid
Normal file
1
src/voxelgame/Voxels/ESCThing/TestStructural.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://br6rfqtryq0cx
|
||||||
BIN
src/voxelgame/Voxels/Registry/.VoxelDefinition.cs.kate-swp
Normal file
BIN
src/voxelgame/Voxels/Registry/.VoxelDefinition.cs.kate-swp
Normal file
Binary file not shown.
21
src/voxelgame/Voxels/Registry/IBitBacking.cs
Normal file
21
src/voxelgame/Voxels/Registry/IBitBacking.cs
Normal 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);
|
||||||
|
|
||||||
|
}
|
||||||
1
src/voxelgame/Voxels/Registry/IBitBacking.cs.uid
Normal file
1
src/voxelgame/Voxels/Registry/IBitBacking.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://bemjbjlx8y6q6
|
||||||
501
src/voxelgame/Voxels/Registry/VoxelDefinition.cs
Normal file
501
src/voxelgame/Voxels/Registry/VoxelDefinition.cs
Normal 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>();
|
||||||
1
src/voxelgame/Voxels/Registry/VoxelDefinition.cs.uid
Normal file
1
src/voxelgame/Voxels/Registry/VoxelDefinition.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://tphqa7lth1n1
|
||||||
64
src/voxelgame/Voxels/Registry/VoxelDefinitionBuilder.cs
Normal file
64
src/voxelgame/Voxels/Registry/VoxelDefinitionBuilder.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://bgd33blsocwb6
|
||||||
38
src/voxelgame/Voxels/Registry/VoxelHandle.cs
Normal file
38
src/voxelgame/Voxels/Registry/VoxelHandle.cs
Normal 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);
|
||||||
|
|
||||||
|
}
|
||||||
1
src/voxelgame/Voxels/Registry/VoxelHandle.cs.uid
Normal file
1
src/voxelgame/Voxels/Registry/VoxelHandle.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://4w8vs8tnuopn
|
||||||
229
src/voxelgame/Voxels/Registry/VoxelInstance.cs
Normal file
229
src/voxelgame/Voxels/Registry/VoxelInstance.cs
Normal 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 instance’s 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 instance’s 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;
|
||||||
|
// }
|
||||||
|
}
|
||||||
1
src/voxelgame/Voxels/Registry/VoxelInstance.cs.uid
Normal file
1
src/voxelgame/Voxels/Registry/VoxelInstance.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://bes4tnuxpkexq
|
||||||
125
src/voxelgame/Voxels/Registry/VoxelInstanceBuilder.cs
Normal file
125
src/voxelgame/Voxels/Registry/VoxelInstanceBuilder.cs
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://b3bek2f2qoo8l
|
||||||
386
src/voxelgame/Voxels/SpatialTree.cs
Normal file
386
src/voxelgame/Voxels/SpatialTree.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
1
src/voxelgame/Voxels/SpatialTree.cs.uid
Normal file
1
src/voxelgame/Voxels/SpatialTree.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://dik0ryeup2wah
|
||||||
14
src/voxelgame/Voxels/TestWraper.tscn
Normal file
14
src/voxelgame/Voxels/TestWraper.tscn
Normal 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
|
||||||
111
src/voxelgame/Voxels/UVWrapVisualizer.cs
Normal file
111
src/voxelgame/Voxels/UVWrapVisualizer.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
1
src/voxelgame/Voxels/UVWrapVisualizer.cs.uid
Normal file
1
src/voxelgame/Voxels/UVWrapVisualizer.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://kdg8pxsdacor
|
||||||
194
src/voxelgame/Voxels/VoxelQuery.cs
Normal file
194
src/voxelgame/Voxels/VoxelQuery.cs
Normal 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>();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
1
src/voxelgame/Voxels/VoxelQuery.cs.uid
Normal file
1
src/voxelgame/Voxels/VoxelQuery.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://f4gkrcovyyc
|
||||||
91
src/voxelgame/Voxels/Worlds/IWorldChunk.cs
Normal file
91
src/voxelgame/Voxels/Worlds/IWorldChunk.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
1
src/voxelgame/Voxels/Worlds/IWorldChunk.cs.uid
Normal file
1
src/voxelgame/Voxels/Worlds/IWorldChunk.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://ct81cqcwbb6wk
|
||||||
860
src/voxelgame/Voxels/Worlds/VoxelSerializer.cs
Normal file
860
src/voxelgame/Voxels/Worlds/VoxelSerializer.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
1
src/voxelgame/Voxels/Worlds/VoxelSerializer.cs.uid
Normal file
1
src/voxelgame/Voxels/Worlds/VoxelSerializer.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://c3rdxjvybaq0x
|
||||||
1087
src/voxelgame/Voxels/Worlds/VoxelWorld.cs
Normal file
1087
src/voxelgame/Voxels/Worlds/VoxelWorld.cs
Normal file
File diff suppressed because it is too large
Load Diff
1
src/voxelgame/Voxels/Worlds/VoxelWorld.cs.uid
Normal file
1
src/voxelgame/Voxels/Worlds/VoxelWorld.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://dtg4km6ikm0qf
|
||||||
30
src/voxelgame/Voxels/clip_plane.gdshader
Normal file
30
src/voxelgame/Voxels/clip_plane.gdshader
Normal 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.
|
||||||
|
//}
|
||||||
1
src/voxelgame/Voxels/clip_plane.gdshader.uid
Normal file
1
src/voxelgame/Voxels/clip_plane.gdshader.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://b0dpogulmm2p7
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://pducp5spu5en
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://baxk18v2sa2l5
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://cfu0pu0m1m22j
|
||||||
@@ -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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://bk1byj8vgtc4e
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://uajav2gns4to
|
||||||
@@ -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)
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://xdjd61gnvv5g
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://cjkfon8d3lvm4
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://bqf0fhngjcpf
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://bp8n8x32b1x5w
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://lsnwo7ujgwf0
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://dfl1b0g22wbck
|
||||||
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://iavcf5e0axvd
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://dnq1yjcsndmtg
|
||||||
@@ -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}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://b2amvjqvxb3gn
|
||||||
@@ -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
Reference in New Issue
Block a user