baseline: working salvaged prototype on Godot 4.7.1 (pre-F1)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
commit
7f0f43b5fb
48 changed files with 2913 additions and 0 deletions
27
Core/README.md
Normal file
27
Core/README.md
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
# Core Module - IslaApocalypse
|
||||
|
||||
**Phase 2 Status:** Active (Voxel Math & Parsing Online)
|
||||
|
||||
## Overview
|
||||
The `Core` directory contains the mathematical foundation, data structures, and parser required to bridge the 2D procedural generation with the 3D Voxel Engine. It is strictly logic-driven and contains no Godot Nodes or visual elements.
|
||||
|
||||
## Key Architecture & Components
|
||||
|
||||
### 1. The Data Bridge (`MapDataParser.cs`)
|
||||
Responsible for deserializing the highly compressed binary `.dat` file generated by the `/Tools` pipeline.
|
||||
* **Outputs:** A `WorldBlueprint` object held in RAM.
|
||||
* **Capabilities:** Reads the 2D heightmap, biome map, and town locations. Crucially, it parses all four tiers of A* road vectors (Highways, Branch Roads, Rugged Roads, Trails) for the Server to use in terrain flattening.
|
||||
|
||||
### 2. Voxel Data Containers (`ChunkData.cs` & `Constants.cs`)
|
||||
* **Chunk Dimensions:** Defined in `Constants.cs`. Currently optimized to `32x32` horizontal with a massive `256` vertical height limit to allow for true AAA-scale mountain ranges without heightmap compression.
|
||||
* **Seamless Borders:** `ChunkData.cs` expands its scalar field arrays (`Densities` and `BlockIDs`) by `+1` on all axes. This allows the Marching Cubes algorithm to sample the neighboring chunk's data, ensuring meshes connect perfectly without gaps.
|
||||
|
||||
### 3. The Math Engine (`MarchingCubes.cs`)
|
||||
Transforms the raw `ChunkData` scalar fields into physical Godot `ArrayMesh` geometry.
|
||||
* **Analytical Normals:** Instead of flat shading, the algorithm calculates exact 3D slope gradients at the corners of each voxel. This produces smooth, realistic lighting across curved terrain.
|
||||
* **Vertex Painting:** Reads the `BlockID` assigned to each voxel corner and applies physical RGB colors directly to the mesh vertices before committing the geometry.
|
||||
|
||||
### 4. Biome & Material Mapping (`BiomePalette.cs`, `BlockRegistry.cs`)
|
||||
Translates the 2D world into physical 3D materials.
|
||||
* **BlockRegistry:** A byte-based lookup table defining physical materials (`SAND`, `STONE`, `WASTELAND_DIRT`, `ASPHALT`).
|
||||
* **BiomePalette Logic:** Uses the 2D `Biome` pixel combined with the 3D `currentY` depth to determine the block type. For example, `Biome.Wasteland` combined with a depth of 0 yields `WASTELAND_DIRT`, while a depth > 4 yields `STONE`.
|
||||
63
Core/Scripts/BiomePalette.cs
Normal file
63
Core/Scripts/BiomePalette.cs
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
using Godot;
|
||||
|
||||
namespace IslaApocalypse.Core
|
||||
{
|
||||
public static class BiomePalette
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines the exact Block ID for a specific voxel in the 3D world based on its depth and biome.
|
||||
/// </summary>
|
||||
/// <param name="biome">The 2D biome pixel from the MapData.</param>
|
||||
/// <param name="surfaceHeight">The maximum height of the terrain at this X/Z coordinate.</param>
|
||||
/// <param name="currentY">The Y coordinate of the specific voxel we are painting.</param>
|
||||
/// <param name="isRoad">Is this coordinate part of the highway/branch road network?</param>
|
||||
public static byte GetVoxelID(Biome biome, int surfaceHeight, int currentY, bool isRoad)
|
||||
{
|
||||
// 1. Air Check: If we are above the terrain, it's Air.
|
||||
if (currentY > surfaceHeight) return BlockRegistry.AIR;
|
||||
|
||||
// 2. Bedrock Check: The very bottom of the world is indestructible.
|
||||
if (currentY == 0) return BlockRegistry.BEDROCK;
|
||||
|
||||
// 3. Infrastructure Check: If this is a road, and we are exactly on the surface, paint asphalt.
|
||||
if (isRoad && currentY == surfaceHeight) return BlockRegistry.ASPHALT;
|
||||
|
||||
int depthBelowSurface = surfaceHeight - currentY;
|
||||
|
||||
// 4. Subsurface Logic (Dirt layer vs Stone layer)
|
||||
// The top 4 blocks are usually dirt/sand, everything below that is solid stone.
|
||||
if (depthBelowSurface > 4) return BlockRegistry.STONE;
|
||||
|
||||
// 5. Surface & Shallow Subsurface Logic (Based on Biome)
|
||||
switch (biome)
|
||||
{
|
||||
case Biome.Beach:
|
||||
case Biome.Crater:
|
||||
return BlockRegistry.SAND; // Craters and Beaches are deep sand
|
||||
|
||||
case Biome.Snow:
|
||||
case Biome.Mountain:
|
||||
if (depthBelowSurface == 0) return BlockRegistry.SNOW; // Only the top layer is snow
|
||||
return BlockRegistry.STONE; // Mountains have shallow topsoil, mostly stone
|
||||
|
||||
case Biome.Wasteland:
|
||||
return BlockRegistry.WASTELAND_DIRT;
|
||||
|
||||
case Biome.Jungle:
|
||||
if (depthBelowSurface == 0) return BlockRegistry.GRASS_JUNGLE;
|
||||
return BlockRegistry.DIRT;
|
||||
|
||||
case Biome.Paradise:
|
||||
if (depthBelowSurface == 0) return BlockRegistry.GRASS_PARADISE;
|
||||
return BlockRegistry.DIRT;
|
||||
|
||||
case Biome.Tropical:
|
||||
if (depthBelowSurface == 0) return BlockRegistry.GRASS_TROPICAL;
|
||||
return BlockRegistry.DIRT;
|
||||
|
||||
default:
|
||||
return BlockRegistry.DIRT; // Fallback
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1
Core/Scripts/BiomePalette.cs.uid
Normal file
1
Core/Scripts/BiomePalette.cs.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://dx8hj4h8v0f2j
|
||||
20
Core/Scripts/BlockData.cs
Normal file
20
Core/Scripts/BlockData.cs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
using Godot;
|
||||
|
||||
namespace IslaApocalypse.Core
|
||||
{
|
||||
public struct BlockData
|
||||
{
|
||||
public byte ID;
|
||||
public string Name;
|
||||
public bool IsSolid;
|
||||
public Color BaseColor; // We use color for now; later we replace this with Texture Array IDs for the Shader
|
||||
|
||||
public BlockData(byte id, string name, bool isSolid, Color baseColor)
|
||||
{
|
||||
ID = id;
|
||||
Name = name;
|
||||
IsSolid = isSolid;
|
||||
BaseColor = baseColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
1
Core/Scripts/BlockData.cs.uid
Normal file
1
Core/Scripts/BlockData.cs.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://bjhct6ctt0j46
|
||||
53
Core/Scripts/BlockRegistry.cs
Normal file
53
Core/Scripts/BlockRegistry.cs
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
using Godot;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace IslaApocalypse.Core
|
||||
{
|
||||
public static class BlockRegistry
|
||||
{
|
||||
// A dictionary holding all the block types in the game, searchable by their byte ID
|
||||
public static readonly Dictionary<byte, BlockData> Blocks = new Dictionary<byte, BlockData>();
|
||||
|
||||
// Hardcoded IDs for easy reference in code
|
||||
public const byte AIR = 0;
|
||||
public const byte BEDROCK = 1;
|
||||
public const byte STONE = 2;
|
||||
public const byte DIRT = 3;
|
||||
public const byte SAND = 4;
|
||||
public const byte GRASS_TROPICAL = 5;
|
||||
public const byte GRASS_JUNGLE = 6;
|
||||
public const byte GRASS_PARADISE = 7;
|
||||
public const byte SNOW = 8;
|
||||
public const byte WASTELAND_DIRT = 9;
|
||||
public const byte ASPHALT = 10; // For our roads!
|
||||
|
||||
// This static constructor runs automatically the first time the registry is accessed
|
||||
static BlockRegistry()
|
||||
{
|
||||
// ID 0 is ALWAYS Air in voxel engines.
|
||||
Blocks.Add(AIR, new BlockData(AIR, "Air", false, Colors.Transparent));
|
||||
|
||||
// The Foundation Blocks
|
||||
Blocks.Add(BEDROCK, new BlockData(BEDROCK, "Bedrock", true, new Color(0.1f, 0.1f, 0.1f)));
|
||||
Blocks.Add(STONE, new BlockData(STONE, "Stone", true, new Color(0.5f, 0.5f, 0.5f)));
|
||||
Blocks.Add(DIRT, new BlockData(DIRT, "Dirt", true, new Color(0.4f, 0.3f, 0.2f)));
|
||||
Blocks.Add(SAND, new BlockData(SAND, "Sand", true, new Color(0.8f, 0.7f, 0.4f)));
|
||||
|
||||
// The Biome Surface Blocks
|
||||
Blocks.Add(GRASS_TROPICAL, new BlockData(GRASS_TROPICAL, "Tropical Grass", true, new Color(0.2f, 0.6f, 0.2f)));
|
||||
Blocks.Add(GRASS_JUNGLE, new BlockData(GRASS_JUNGLE, "Jungle Grass", true, new Color(0.1f, 0.4f, 0.1f)));
|
||||
Blocks.Add(GRASS_PARADISE, new BlockData(GRASS_PARADISE, "Paradise Grass", true, new Color(0.4f, 0.8f, 0.4f)));
|
||||
Blocks.Add(SNOW, new BlockData(SNOW, "Snow", true, new Color(0.9f, 0.9f, 0.9f)));
|
||||
Blocks.Add(WASTELAND_DIRT, new BlockData(WASTELAND_DIRT, "Wasteland Dirt", true, new Color(0.5f, 0.2f, 0.5f)));
|
||||
|
||||
// Infrastructure
|
||||
Blocks.Add(ASPHALT, new BlockData(ASPHALT, "Asphalt", true, new Color(0.15f, 0.15f, 0.15f)));
|
||||
}
|
||||
|
||||
public static BlockData GetBlock(byte id)
|
||||
{
|
||||
if (Blocks.TryGetValue(id, out BlockData data)) return data;
|
||||
return Blocks[AIR]; // Safe fallback so the game doesn't crash if an ID is missing
|
||||
}
|
||||
}
|
||||
}
|
||||
1
Core/Scripts/BlockRegistry.cs.uid
Normal file
1
Core/Scripts/BlockRegistry.cs.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://c2145lfqj00uk
|
||||
29
Core/Scripts/ChunkData.cs
Normal file
29
Core/Scripts/ChunkData.cs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
using Godot;
|
||||
|
||||
namespace IslaApocalypse.Core
|
||||
{
|
||||
public class ChunkData
|
||||
{
|
||||
public Vector2I ChunkPosition; // The X/Z coordinate of this chunk (e.g., 0,0 or 1,0)
|
||||
|
||||
// We need Width+1 and Depth+1 so the Marching Cubes mesh connects seamlessly to the neighbor chunks!
|
||||
// public float[,,] Densities = new float[Constants.CHUNK_SIZE_X + 1, Constants.CHUNK_HEIGHT, Constants.CHUNK_SIZE_Z + 1];
|
||||
// public byte[,,] BlockIDs = new byte[Constants.CHUNK_SIZE_X + 1, Constants.CHUNK_HEIGHT, Constants.CHUNK_SIZE_Z + 1];
|
||||
// OLD:
|
||||
// Densities = new float[Constants.CHUNK_SIZE_X, Constants.CHUNK_HEIGHT, Constants.CHUNK_SIZE_Z];
|
||||
// BlockIDs = new int[Constants.CHUNK_SIZE_X, Constants.CHUNK_HEIGHT, Constants.CHUNK_SIZE_Z];
|
||||
|
||||
// NEW (Fix 3): We expand by 1 to sample the neighbor's border perfectly without seams!
|
||||
public float[,,] Densities = new float[Constants.CHUNK_SIZE_X + 1, Constants.CHUNK_HEIGHT + 1, Constants.CHUNK_SIZE_Z + 1];
|
||||
public byte[,,] BlockIDs = new byte[Constants.CHUNK_SIZE_X + 1, Constants.CHUNK_HEIGHT + 1, Constants.CHUNK_SIZE_Z + 1];
|
||||
|
||||
// For our future delta-save system
|
||||
public bool IsPlayerProtected = false;
|
||||
public bool NeedsSaving = false;
|
||||
|
||||
public ChunkData(Vector2I position)
|
||||
{
|
||||
ChunkPosition = position;
|
||||
}
|
||||
}
|
||||
}
|
||||
1
Core/Scripts/ChunkData.cs.uid
Normal file
1
Core/Scripts/ChunkData.cs.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://cturk2xl2lod
|
||||
111
Core/Scripts/ConfigManager.cs
Normal file
111
Core/Scripts/ConfigManager.cs
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
|
||||
using Godot;
|
||||
using Godot.Collections; // Required for Godot's built-in JSON parser
|
||||
|
||||
namespace IslaApocalypse.Core // Change this if your namespace is different
|
||||
{
|
||||
public static class ConfigManager
|
||||
{
|
||||
// Default Fallbacks
|
||||
public static int WorldSeed = 1063685222;
|
||||
// public static int WorldSeed = (int)GD.Randi(); if 0 also randomizes
|
||||
public static int MapSize = 8192;
|
||||
public static float CraterRadius = 400f;
|
||||
public static float DensityMultiplier = 1.0f; // <-- Replaces TownCount
|
||||
public static int ChunkRadius = 24; // Default chunk size, can be overridden by config
|
||||
|
||||
public static void LoadConfig()
|
||||
{
|
||||
string path = "res://ServerConfig.json";
|
||||
|
||||
if (!FileAccess.FileExists(path))
|
||||
{
|
||||
GD.PrintErr("[ConfigManager] ServerConfig.json not found! Defaulting to 8K.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Read the file
|
||||
using var file = FileAccess.Open(path, FileAccess.ModeFlags.Read);
|
||||
string content = file.GetAsText();
|
||||
|
||||
// Parse the JSON
|
||||
var json = new Json();
|
||||
var error = json.Parse(content);
|
||||
if (error != Error.Ok)
|
||||
{
|
||||
GD.PrintErr($"[ConfigManager] JSON Parse Error: {json.GetErrorMessage()}");
|
||||
return;
|
||||
}
|
||||
|
||||
var data = (Dictionary)json.Data;
|
||||
|
||||
// Extract the Seed
|
||||
if (data.ContainsKey("WorldSeed"))
|
||||
{
|
||||
WorldSeed = (int)data["WorldSeed"];
|
||||
}
|
||||
|
||||
// Extract the MapProfile and run your Switch/Case logic!
|
||||
string profile = "8K";
|
||||
if (data.ContainsKey("MapProfile"))
|
||||
{
|
||||
profile = (string)data["MapProfile"];
|
||||
}
|
||||
|
||||
// Extract the TownDensity and run your Switch/Case logic!
|
||||
string townDensity = "Normal";
|
||||
if (data.ContainsKey("TownDensity"))
|
||||
{
|
||||
townDensity = (string)data["TownDensity"];
|
||||
}
|
||||
// Extract the ChunkRadius
|
||||
if (data.ContainsKey("ChunkRadius")) {
|
||||
ChunkRadius = (int)data["ChunkRadius"];
|
||||
}
|
||||
|
||||
switch (profile)
|
||||
{
|
||||
case "4K":
|
||||
MapSize = 4096;
|
||||
CraterRadius = 400f; // Scaled down crater
|
||||
break;
|
||||
case "6K":
|
||||
MapSize = 6144;
|
||||
CraterRadius = 600f;
|
||||
break;
|
||||
case "8K":
|
||||
MapSize = 8192;
|
||||
CraterRadius = 800f; // Your original crater size spread over an 8K map
|
||||
break;
|
||||
case "10K":
|
||||
MapSize = 10240;
|
||||
CraterRadius = 1000f;
|
||||
break;
|
||||
default:
|
||||
GD.PrintErr($"[ConfigManager] Unknown MapProfile '{profile}'. Defaulting to 8K.");
|
||||
MapSize = 8192;
|
||||
CraterRadius = 800f;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (townDensity)
|
||||
{
|
||||
case "Sparse":
|
||||
DensityMultiplier = 0.5f;
|
||||
break;
|
||||
case "Normal":
|
||||
DensityMultiplier = 1.0f;
|
||||
break;
|
||||
case "Dense":
|
||||
DensityMultiplier = 2.0f;
|
||||
break;
|
||||
default:
|
||||
GD.PrintErr($"[ConfigManager] Unknown TownDensity '{townDensity}'. Defaulting to Normal.");
|
||||
DensityMultiplier = 1.0f;
|
||||
break;
|
||||
}
|
||||
|
||||
GD.Print($"[ConfigManager] Successfully loaded {profile} Profile. MapSize set to {MapSize}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
1
Core/Scripts/ConfigManager.cs.uid
Normal file
1
Core/Scripts/ConfigManager.cs.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://dttxbmvvhlvkl
|
||||
14
Core/Scripts/Constants.cs
Normal file
14
Core/Scripts/Constants.cs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
namespace IslaApocalypse.Core
|
||||
{
|
||||
public static class Constants
|
||||
{
|
||||
// Chunk Dimensions
|
||||
public const int CHUNK_SIZE_X = 24;
|
||||
public const int CHUNK_SIZE_Z = 24;
|
||||
public const int CHUNK_HEIGHT = 256; // Our agreed-upon depth!
|
||||
|
||||
// World Generation
|
||||
public const float ISO_LEVEL = 0.0f; // The threshold for Marching Cubes
|
||||
public const float VOXEL_SCALE = 1.0f; // 1 Pixel = 1 Meter
|
||||
}
|
||||
}
|
||||
1
Core/Scripts/Constants.cs.uid
Normal file
1
Core/Scripts/Constants.cs.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://dhnft3c7f6ek2
|
||||
8
Core/Scripts/Enums.cs
Normal file
8
Core/Scripts/Enums.cs
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
namespace IslaApocalypse.Core
|
||||
{
|
||||
// The master list of shared identifiers for the game
|
||||
public enum Biome { Ocean, Lake, Beach, Paradise, Tropical, Jungle, Wasteland, Crater, Mountain, Snow }
|
||||
public enum TownTier { Village, Hub, Capitol, Outpost, IslandLoot, MiniPOI }
|
||||
public enum MapHalf { Any, Left, Right }
|
||||
|
||||
}
|
||||
1
Core/Scripts/Enums.cs.uid
Normal file
1
Core/Scripts/Enums.cs.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://b573cexjylgn8
|
||||
55
Core/Scripts/MATH_MARCHING_CUBES.md
Normal file
55
Core/Scripts/MATH_MARCHING_CUBES.md
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
# 📐 The Math of Marching Cubes (Smooth Voxels)
|
||||
|
||||
## Overview
|
||||
In a standard blocky game like *Minecraft* (Greedy Meshing), if a coordinate has a block, you draw a square. If it doesn't, you draw nothing.
|
||||
|
||||
In a smooth voxel game like *7 Days to Die*, we use an algorithm called **Marching Cubes**. Instead of looking at a single block, the algorithm looks at the **8 corners** of an invisible 3D grid cell (a cube) and asks: *"Which of these 8 corners are underground, and which are in the air?"*
|
||||
|
||||
Based on the answer, it draws a specific set of triangles *through* the invisible cube to create a smooth surface.
|
||||
|
||||
---
|
||||
|
||||
## 🔑 Key Concepts
|
||||
|
||||
### 1. Density (The "Float" Value)
|
||||
In smooth voxels, a point in space isn't just "Solid" (1) or "Empty" (0). It has a continuous **Density** value.
|
||||
* ` 1.0` = Deep underground (Solid Rock).
|
||||
* ` 0.5` = Slightly underground (Dirt).
|
||||
* ` 0.0` = The exact surface of the ground.
|
||||
* `-0.5` = Slightly above ground (Air).
|
||||
* `-1.0` = High in the sky (Empty Space).
|
||||
|
||||
### 2. The Iso-Level (The Threshold)
|
||||
The Iso-Level is the exact density value where the "skin" of the world is drawn. Usually, this is `0.0`.
|
||||
* If a corner's density is **greater** than `0.0`, it is "Inside" the terrain.
|
||||
* If a corner's density is **less** than `0.0`, it is "Outside" in the air.
|
||||
|
||||
### 3. The Triangulation Table (The Magic Array)
|
||||
A cube has 8 corners. Each corner can either be Inside or Outside.
|
||||
That means there are **2^8 = 256 possible combinations**.
|
||||
|
||||
Instead of writing 256 `if/else` statements, the algorithm uses a hardcoded **Lookup Table** (an array of integers).
|
||||
* *Example:* If corner 0 is inside, but corners 1-7 are outside, the table instantly says: *"Draw a single small triangle slicing off corner 0."*
|
||||
* *Example:* If corners 0, 1, 2, and 3 are inside (the whole bottom half), the table says: *"Draw a flat quad (two triangles) straight across the middle of the cube."*
|
||||
|
||||
### 4. Linear Interpolation (Why it looks Smooth)
|
||||
If the algorithm just drew triangles exactly halfway between the inside and outside corners, the terrain would still look a bit jagged (like a low-poly PS1 game).
|
||||
|
||||
To make it perfectly smooth, we **Interpolate**:
|
||||
* Corner A has a density of `1.0` (Very solid).
|
||||
* Corner B has a density of `-0.1` (Just barely in the air).
|
||||
* Because `-0.1` is much closer to our `0.0` Iso-Level than `1.0` is, the algorithm slides the triangle vertex much closer to Corner B. This creates gentle slopes and sharp cliffs dynamically.
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ The Execution Loop (What our C# Script will do)
|
||||
|
||||
When the Server tells the Client to render a 16x16x64 chunk, the mesher does this:
|
||||
1. Loops through every X, Y, Z coordinate in the chunk.
|
||||
2. Checks the 8 corners of the current voxel.
|
||||
3. Creates an `8-bit integer` (a byte) based on which corners are solid (e.g., `00001111`).
|
||||
4. Plugs that byte into the **Triangulation Table**.
|
||||
5. The table spits out a list of edges.
|
||||
6. The script calculates the exact interpolated point on those edges.
|
||||
7. It connects those points into triangles and adds them to Godot's `ArrayMesh`.
|
||||
8. *It Marches* to the next cube and repeats.
|
||||
129
Core/Scripts/MapDataParser.cs
Normal file
129
Core/Scripts/MapDataParser.cs
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
using Godot;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace IslaApocalypse.Core
|
||||
{
|
||||
// 1. The Data Container
|
||||
// This holds the unpacked data in RAM so the Server can ask it questions
|
||||
// without having to re-read the hard drive every single frame.
|
||||
public struct TownLocation
|
||||
{
|
||||
public Vector2 Position;
|
||||
public TownTier Tier;
|
||||
}
|
||||
|
||||
public class WorldBlueprint
|
||||
{
|
||||
public int MapSize;
|
||||
public float[,] HeightMap;
|
||||
public Biome[,] BiomeMap;
|
||||
public List<TownLocation> Towns = new List<TownLocation>();
|
||||
|
||||
// All 4 road tiers!
|
||||
public List<Vector2[]> Highways = new List<Vector2[]>();
|
||||
public List<Vector2[]> BranchRoads = new List<Vector2[]>();
|
||||
public List<Vector2[]> RuggedRoads = new List<Vector2[]>();
|
||||
public List<Vector2[]> TrailRoads = new List<Vector2[]>();
|
||||
}
|
||||
|
||||
// 2. The Parser Utility
|
||||
public static class MapDataParser
|
||||
{
|
||||
public static WorldBlueprint LoadMapData(string seedStr)
|
||||
{
|
||||
string filePath = ProjectSettings.GlobalizePath($"user://MapData_Seed_{seedStr}.dat");
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
GD.PrintErr($"[MapDataParser] CRITICAL ERROR: Map file not found at {filePath}");
|
||||
return null;
|
||||
}
|
||||
|
||||
WorldBlueprint blueprint = new WorldBlueprint();
|
||||
|
||||
using (FileStream stream = File.OpenRead(filePath))
|
||||
{
|
||||
using (BinaryReader reader = new BinaryReader(stream))
|
||||
{
|
||||
// 1. Header Validation (CRITICAL: Must read string first to align bytes!)
|
||||
string header = reader.ReadString();
|
||||
if (header != "ISLA_V1")
|
||||
{
|
||||
GD.PrintErr("[MapDataParser] ERROR: Invalid map version or corrupted file.");
|
||||
return null;
|
||||
}
|
||||
|
||||
// 2. Read Map Dimensions & Initialize Arrays
|
||||
blueprint.MapSize = reader.ReadInt32();
|
||||
blueprint.HeightMap = new float[blueprint.MapSize, blueprint.MapSize];
|
||||
blueprint.BiomeMap = new Biome[blueprint.MapSize, blueprint.MapSize];
|
||||
|
||||
// 3. Read Raw Voxel Data (Height & Biome)
|
||||
for (int x = 0; x < blueprint.MapSize; x++)
|
||||
{
|
||||
for (int y = 0; y < blueprint.MapSize; y++)
|
||||
{
|
||||
blueprint.HeightMap[x, y] = reader.ReadSingle();
|
||||
blueprint.BiomeMap[x, y] = (Biome)reader.ReadInt32();
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Read Logistical Anchors (Towns & POIs)
|
||||
int townCount = reader.ReadInt32();
|
||||
for (int i = 0; i < townCount; i++)
|
||||
{
|
||||
TownLocation town = new TownLocation();
|
||||
town.Position = new Vector2(reader.ReadSingle(), reader.ReadSingle());
|
||||
town.Tier = (TownTier)reader.ReadInt32();
|
||||
blueprint.Towns.Add(town);
|
||||
}
|
||||
|
||||
// 5. Read Road Networks
|
||||
// Highway
|
||||
int highwayCount = reader.ReadInt32();
|
||||
for (int i = 0; i < highwayCount; i++)
|
||||
{
|
||||
int pathLength = reader.ReadInt32();
|
||||
Vector2[] path = new Vector2[pathLength];
|
||||
for (int p = 0; p < pathLength; p++) { path[p] = new Vector2(reader.ReadSingle(), reader.ReadSingle()); }
|
||||
blueprint.Highways.Add(path);
|
||||
}
|
||||
|
||||
// Branch
|
||||
int branchCount = reader.ReadInt32();
|
||||
for (int i = 0; i < branchCount; i++)
|
||||
{
|
||||
int pathLength = reader.ReadInt32();
|
||||
Vector2[] path = new Vector2[pathLength];
|
||||
for (int p = 0; p < pathLength; p++) { path[p] = new Vector2(reader.ReadSingle(), reader.ReadSingle()); }
|
||||
blueprint.BranchRoads.Add(path);
|
||||
}
|
||||
|
||||
// Rugged
|
||||
int ruggedCount = reader.ReadInt32();
|
||||
for (int i = 0; i < ruggedCount; i++)
|
||||
{
|
||||
int pathLength = reader.ReadInt32();
|
||||
Vector2[] path = new Vector2[pathLength];
|
||||
for (int p = 0; p < pathLength; p++) { path[p] = new Vector2(reader.ReadSingle(), reader.ReadSingle()); }
|
||||
blueprint.RuggedRoads.Add(path);
|
||||
}
|
||||
|
||||
// Trails
|
||||
int trailCount = reader.ReadInt32();
|
||||
for (int i = 0; i < trailCount; i++)
|
||||
{
|
||||
int pathLength = reader.ReadInt32();
|
||||
Vector2[] path = new Vector2[pathLength];
|
||||
for (int p = 0; p < pathLength; p++) { path[p] = new Vector2(reader.ReadSingle(), reader.ReadSingle()); }
|
||||
blueprint.TrailRoads.Add(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GD.Print($"[MapDataParser] Successfully loaded Blueprint for Seed {seedStr}. Dimension: {blueprint.MapSize}x{blueprint.MapSize}.");
|
||||
return blueprint;
|
||||
}
|
||||
}
|
||||
}
|
||||
1
Core/Scripts/MapDataParser.cs.uid
Normal file
1
Core/Scripts/MapDataParser.cs.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://6qednxmubxia
|
||||
522
Core/Scripts/MarchingCubes.cs
Normal file
522
Core/Scripts/MarchingCubes.cs
Normal file
|
|
@ -0,0 +1,522 @@
|
|||
using Godot;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace IslaApocalypse.Core
|
||||
{
|
||||
public static class MarchingCubes
|
||||
{
|
||||
/// <summary>
|
||||
/// The Edge Table (256 values).
|
||||
/// This array uses an 8-bit index (representing the 8 corners of a cube)
|
||||
/// to look up a 12-bit value. Each bit in the returned value corresponds to
|
||||
/// one of the 12 edges of the cube, telling the engine if that edge is intersected.
|
||||
/// </summary>
|
||||
public static readonly int[] EdgeTable = new int[256] {
|
||||
0x0 , 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c,
|
||||
0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00,
|
||||
0x190, 0x99 , 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c,
|
||||
0x99c, 0x895, 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90,
|
||||
0x230, 0x339, 0x33 , 0x13a, 0x636, 0x73f, 0x435, 0x53c,
|
||||
0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30,
|
||||
0x3a0, 0x2a9, 0x1a3, 0xaa , 0x7a6, 0x6af, 0x5a5, 0x4ac,
|
||||
0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0,
|
||||
0x460, 0x569, 0x663, 0x76a, 0x66 , 0x16f, 0x265, 0x36c,
|
||||
0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963, 0xa69, 0xb60,
|
||||
0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0xff , 0x3f5, 0x2fc,
|
||||
0xdfc, 0xcf5, 0xfff, 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0,
|
||||
0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x55 , 0x15c,
|
||||
0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950,
|
||||
0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6, 0x2cf, 0x1c5, 0xcc ,
|
||||
0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0,
|
||||
0x8c0, 0x9c9, 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc,
|
||||
0xcc , 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9, 0x7c0,
|
||||
0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c,
|
||||
0x15c, 0x55 , 0x35f, 0x256, 0x55a, 0x453, 0x759, 0x650,
|
||||
0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc,
|
||||
0x2fc, 0x3f5, 0xff , 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0,
|
||||
0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f, 0xd65, 0xc6c,
|
||||
0x36c, 0x265, 0x16f, 0x66 , 0x76a, 0x663, 0x569, 0x460,
|
||||
0xca0, 0xda9, 0xea3, 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac,
|
||||
0x4ac, 0x5a5, 0x6af, 0x7a6, 0xaa , 0x1a3, 0x2a9, 0x3a0,
|
||||
0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x83f, 0xb35, 0xa3c,
|
||||
0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x33 , 0x339, 0x230,
|
||||
0xe90, 0xf99, 0xc93, 0xd9a, 0xa96, 0xb9f, 0x895, 0x99c,
|
||||
0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x99 , 0x190,
|
||||
0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c,
|
||||
0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109, 0x0
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The Triangulation Table (256 rows, 16 columns).
|
||||
/// Once we know which edges are intersected, this table tells us exactly
|
||||
/// how to connect those intersections to draw up to 5 triangles.
|
||||
/// A value of -1 means "stop drawing triangles".
|
||||
/// </summary>
|
||||
public static readonly int[,] TriTable = new int[256, 16] {
|
||||
{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{0, 1, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{1, 8, 3, 9, 8, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{0, 8, 3, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{9, 2, 10, 0, 2, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{2, 8, 3, 2, 10, 8, 10, 9, 8, -1, -1, -1, -1, -1, -1, -1},
|
||||
{3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{0, 11, 2, 8, 11, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{1, 9, 0, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{1, 11, 2, 1, 9, 11, 9, 8, 11, -1, -1, -1, -1, -1, -1, -1},
|
||||
{3, 10, 1, 11, 10, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{0, 10, 1, 0, 8, 10, 8, 11, 10, -1, -1, -1, -1, -1, -1, -1},
|
||||
{3, 9, 0, 3, 11, 9, 11, 10, 9, -1, -1, -1, -1, -1, -1, -1},
|
||||
{9, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{4, 3, 0, 7, 3, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{0, 1, 9, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{4, 1, 9, 4, 7, 1, 7, 3, 1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{1, 2, 10, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{3, 4, 7, 3, 0, 4, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1},
|
||||
{9, 2, 10, 9, 0, 2, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1},
|
||||
{2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4, -1, -1, -1, -1},
|
||||
{8, 4, 7, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{11, 4, 7, 11, 2, 4, 2, 0, 4, -1, -1, -1, -1, -1, -1, -1},
|
||||
{9, 0, 1, 8, 4, 7, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1},
|
||||
{4, 7, 11, 9, 4, 11, 9, 11, 2, 9, 2, 1, -1, -1, -1, -1},
|
||||
{3, 10, 1, 3, 11, 10, 7, 8, 4, -1, -1, -1, -1, -1, -1, -1},
|
||||
{1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4, -1, -1, -1, -1},
|
||||
{4, 7, 8, 9, 0, 11, 9, 11, 10, 11, 0, 3, -1, -1, -1, -1},
|
||||
{4, 7, 11, 4, 11, 9, 9, 11, 10, -1, -1, -1, -1, -1, -1, -1},
|
||||
{9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{9, 5, 4, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{0, 5, 4, 1, 5, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{8, 5, 4, 8, 3, 5, 3, 1, 5, -1, -1, -1, -1, -1, -1, -1},
|
||||
{1, 2, 10, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{3, 0, 8, 1, 2, 10, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1},
|
||||
{5, 2, 10, 5, 4, 2, 4, 0, 2, -1, -1, -1, -1, -1, -1, -1},
|
||||
{2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8, -1, -1, -1, -1},
|
||||
{9, 5, 4, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{0, 11, 2, 0, 8, 11, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1},
|
||||
{0, 5, 4, 0, 1, 5, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1},
|
||||
{2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5, -1, -1, -1, -1},
|
||||
{10, 3, 11, 10, 1, 3, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1},
|
||||
{4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10, -1, -1, -1, -1},
|
||||
{5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3, -1, -1, -1, -1},
|
||||
{5, 4, 8, 5, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1},
|
||||
{9, 7, 8, 5, 7, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{9, 3, 0, 9, 5, 3, 5, 7, 3, -1, -1, -1, -1, -1, -1, -1},
|
||||
{0, 7, 8, 0, 1, 7, 1, 5, 7, -1, -1, -1, -1, -1, -1, -1},
|
||||
{1, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{9, 7, 8, 9, 5, 7, 10, 1, 2, -1, -1, -1, -1, -1, -1, -1},
|
||||
{10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3, -1, -1, -1, -1},
|
||||
{8, 0, 2, 8, 2, 5, 8, 5, 7, 10, 5, 2, -1, -1, -1, -1},
|
||||
{2, 10, 5, 2, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1},
|
||||
{7, 9, 5, 7, 8, 9, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1},
|
||||
{9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7, 11, -1, -1, -1, -1},
|
||||
{2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7, -1, -1, -1, -1},
|
||||
{11, 2, 1, 11, 1, 7, 7, 1, 5, -1, -1, -1, -1, -1, -1, -1},
|
||||
{9, 5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11, -1, -1, -1, -1},
|
||||
{5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0, -1},
|
||||
{11, 10, 0, 11, 0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0, -1},
|
||||
{11, 10, 5, 7, 11, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{0, 8, 3, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{9, 0, 1, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{1, 8, 3, 1, 9, 8, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1},
|
||||
{1, 6, 5, 2, 6, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{1, 6, 5, 1, 2, 6, 3, 0, 8, -1, -1, -1, -1, -1, -1, -1},
|
||||
{9, 6, 5, 9, 0, 6, 0, 2, 6, -1, -1, -1, -1, -1, -1, -1},
|
||||
{5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8, -1, -1, -1, -1},
|
||||
{2, 3, 11, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{11, 0, 8, 11, 2, 0, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1},
|
||||
{0, 1, 9, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1},
|
||||
{5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11, -1, -1, -1, -1},
|
||||
{6, 3, 11, 6, 5, 3, 5, 1, 3, -1, -1, -1, -1, -1, -1, -1},
|
||||
{0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6, -1, -1, -1, -1},
|
||||
{3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9, -1, -1, -1, -1},
|
||||
{6, 5, 9, 6, 9, 11, 11, 9, 8, -1, -1, -1, -1, -1, -1, -1},
|
||||
{5, 10, 6, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{4, 3, 0, 4, 7, 3, 6, 5, 10, -1, -1, -1, -1, -1, -1, -1},
|
||||
{1, 9, 0, 5, 10, 6, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1},
|
||||
{10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4, -1, -1, -1, -1},
|
||||
{6, 1, 2, 6, 5, 1, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1},
|
||||
{1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7, -1, -1, -1, -1},
|
||||
{8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6, -1, -1, -1, -1},
|
||||
{7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9, -1},
|
||||
{3, 11, 2, 7, 8, 4, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1},
|
||||
{5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11, -1, -1, -1, -1},
|
||||
{0, 1, 9, 4, 7, 8, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1},
|
||||
{9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6, -1},
|
||||
{8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6, -1, -1, -1, -1},
|
||||
{5, 1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11, -1},
|
||||
{0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7, -1},
|
||||
{6, 5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9, -1, -1, -1, -1},
|
||||
{10, 4, 9, 6, 4, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{4, 10, 6, 4, 9, 10, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1},
|
||||
{10, 0, 1, 10, 6, 0, 6, 4, 0, -1, -1, -1, -1, -1, -1, -1},
|
||||
{8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10, -1, -1, -1, -1},
|
||||
{1, 4, 9, 1, 2, 4, 2, 6, 4, -1, -1, -1, -1, -1, -1, -1},
|
||||
{3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4, -1, -1, -1, -1},
|
||||
{0, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{8, 3, 2, 8, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1},
|
||||
{10, 4, 9, 10, 6, 4, 11, 2, 3, -1, -1, -1, -1, -1, -1, -1},
|
||||
{0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6, -1, -1, -1, -1},
|
||||
{3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10, -1, -1, -1, -1},
|
||||
{6, 4, 1, 6, 1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1, -1},
|
||||
{9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3, -1, -1, -1, -1},
|
||||
{8, 11, 1, 8, 1, 0, 11, 6, 1, 9, 1, 4, 6, 4, 1, -1},
|
||||
{3, 11, 6, 3, 6, 0, 0, 6, 4, -1, -1, -1, -1, -1, -1, -1},
|
||||
{6, 4, 8, 11, 6, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{7, 10, 6, 7, 8, 10, 8, 9, 10, -1, -1, -1, -1, -1, -1, -1},
|
||||
{0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10, -1, -1, -1, -1},
|
||||
{10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0, -1, -1, -1, -1},
|
||||
{10, 6, 7, 10, 7, 1, 1, 7, 3, -1, -1, -1, -1, -1, -1, -1},
|
||||
{1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7, -1, -1, -1, -1},
|
||||
{2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9, -1},
|
||||
{7, 8, 0, 7, 0, 6, 6, 0, 2, -1, -1, -1, -1, -1, -1, -1},
|
||||
{7, 3, 2, 6, 7, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7, -1, -1, -1, -1},
|
||||
{2, 0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7, -1},
|
||||
{1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11, -1},
|
||||
{11, 2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1, -1, -1, -1, -1},
|
||||
{8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6, -1},
|
||||
{0, 9, 1, 11, 6, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0, -1, -1, -1, -1},
|
||||
{7, 11, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{3, 0, 8, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{0, 1, 9, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{8, 1, 9, 8, 3, 1, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1},
|
||||
{10, 1, 2, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{1, 2, 10, 3, 0, 8, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1},
|
||||
{2, 9, 0, 2, 10, 9, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1},
|
||||
{6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8, -1, -1, -1, -1},
|
||||
{7, 2, 3, 6, 2, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{7, 0, 8, 7, 6, 0, 6, 2, 0, -1, -1, -1, -1, -1, -1, -1},
|
||||
{2, 7, 6, 2, 3, 7, 0, 1, 9, -1, -1, -1, -1, -1, -1, -1},
|
||||
{1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6, -1, -1, -1, -1},
|
||||
{10, 7, 6, 10, 1, 7, 1, 3, 7, -1, -1, -1, -1, -1, -1, -1},
|
||||
{10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8, -1, -1, -1, -1},
|
||||
{0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7, -1, -1, -1, -1},
|
||||
{7, 6, 10, 7, 10, 8, 8, 10, 9, -1, -1, -1, -1, -1, -1, -1},
|
||||
{6, 8, 4, 11, 8, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{3, 6, 11, 3, 0, 6, 0, 4, 6, -1, -1, -1, -1, -1, -1, -1},
|
||||
{8, 6, 11, 8, 4, 6, 9, 0, 1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{9, 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6, -1, -1, -1, -1},
|
||||
{6, 8, 4, 6, 11, 8, 2, 10, 1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{1, 2, 10, 3, 0, 11, 0, 6, 11, 0, 4, 6, -1, -1, -1, -1},
|
||||
{4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9, -1, -1, -1, -1},
|
||||
{10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3, -1},
|
||||
{8, 2, 3, 8, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1},
|
||||
{0, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8, -1, -1, -1, -1},
|
||||
{1, 9, 4, 1, 4, 2, 2, 4, 6, -1, -1, -1, -1, -1, -1, -1},
|
||||
{8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1, -1, -1, -1, -1},
|
||||
{10, 1, 0, 10, 0, 6, 6, 0, 4, -1, -1, -1, -1, -1, -1, -1},
|
||||
{4, 6, 3, 4, 3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3, -1},
|
||||
{10, 9, 4, 6, 10, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{4, 9, 5, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{0, 8, 3, 4, 9, 5, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1},
|
||||
{5, 0, 1, 5, 4, 0, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1},
|
||||
{11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5, -1, -1, -1, -1},
|
||||
{9, 5, 4, 10, 1, 2, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1},
|
||||
{6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5, -1, -1, -1, -1},
|
||||
{7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2, -1, -1, -1, -1},
|
||||
{3, 4, 8, 3, 5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6, -1},
|
||||
{7, 2, 3, 7, 6, 2, 5, 4, 9, -1, -1, -1, -1, -1, -1, -1},
|
||||
{9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7, -1, -1, -1, -1},
|
||||
{3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0, -1, -1, -1, -1},
|
||||
{6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8, -1},
|
||||
{9, 5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7, -1, -1, -1, -1},
|
||||
{1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4, -1},
|
||||
{4, 0, 10, 4, 10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10, -1},
|
||||
{7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10, -1, -1, -1, -1},
|
||||
{6, 9, 5, 6, 11, 9, 11, 8, 9, -1, -1, -1, -1, -1, -1, -1},
|
||||
{3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5, -1, -1, -1, -1},
|
||||
{0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11, -1, -1, -1, -1},
|
||||
{6, 11, 3, 6, 3, 5, 5, 3, 1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6, -1, -1, -1, -1},
|
||||
{0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1, 2, 10, -1},
|
||||
{11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5, -1},
|
||||
{6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3, -1, -1, -1, -1},
|
||||
{5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2, -1, -1, -1, -1},
|
||||
{9, 5, 6, 9, 6, 0, 0, 6, 2, -1, -1, -1, -1, -1, -1, -1},
|
||||
{1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8, -1},
|
||||
{1, 5, 6, 2, 1, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6, -1},
|
||||
{10, 1, 0, 10, 0, 6, 9, 5, 0, 5, 6, 0, -1, -1, -1, -1},
|
||||
{0, 3, 8, 5, 6, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{10, 5, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{11, 5, 10, 7, 5, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{11, 5, 10, 11, 7, 5, 8, 3, 0, -1, -1, -1, -1, -1, -1, -1},
|
||||
{5, 11, 7, 5, 10, 11, 1, 9, 0, -1, -1, -1, -1, -1, -1, -1},
|
||||
{10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1, -1, -1, -1, -1},
|
||||
{11, 1, 2, 11, 7, 1, 7, 5, 1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11, -1, -1, -1, -1},
|
||||
{9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7, -1, -1, -1, -1},
|
||||
{7, 5, 2, 7, 2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2, -1},
|
||||
{2, 5, 10, 2, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1},
|
||||
{8, 2, 0, 8, 5, 2, 8, 7, 5, 10, 2, 5, -1, -1, -1, -1},
|
||||
{9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2, -1, -1, -1, -1},
|
||||
{9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2, -1},
|
||||
{1, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{0, 8, 7, 0, 7, 1, 1, 7, 5, -1, -1, -1, -1, -1, -1, -1},
|
||||
{9, 0, 3, 9, 3, 5, 5, 3, 7, -1, -1, -1, -1, -1, -1, -1},
|
||||
{9, 8, 7, 5, 9, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{5, 8, 4, 5, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1},
|
||||
{5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0, -1, -1, -1, -1},
|
||||
{0, 1, 9, 8, 4, 10, 8, 10, 11, 10, 4, 5, -1, -1, -1, -1},
|
||||
{10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4, -1},
|
||||
{2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8, -1, -1, -1, -1},
|
||||
{0, 4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11, -1},
|
||||
{0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5, -1},
|
||||
{9, 4, 5, 2, 11, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4, -1, -1, -1, -1},
|
||||
{5, 10, 2, 5, 2, 4, 4, 2, 0, -1, -1, -1, -1, -1, -1, -1},
|
||||
{3, 10, 2, 3, 5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9, -1},
|
||||
{5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2, -1, -1, -1, -1},
|
||||
{8, 4, 5, 8, 5, 3, 3, 5, 1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{0, 4, 5, 1, 0, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5, -1, -1, -1, -1},
|
||||
{9, 4, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{4, 11, 7, 4, 9, 11, 9, 10, 11, -1, -1, -1, -1, -1, -1, -1},
|
||||
{0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11, -1, -1, -1, -1},
|
||||
{1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11, -1, -1, -1, -1},
|
||||
{3, 1, 4, 3, 4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4, -1},
|
||||
{4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2, -1, -1, -1, -1},
|
||||
{9, 7, 4, 9, 11, 7, 9, 1, 11, 2, 11, 1, 0, 8, 3, -1},
|
||||
{11, 7, 4, 11, 4, 2, 2, 4, 0, -1, -1, -1, -1, -1, -1, -1},
|
||||
{11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4, -1, -1, -1, -1},
|
||||
{2, 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9, -1, -1, -1, -1},
|
||||
{9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7, -1},
|
||||
{3, 7, 10, 3, 10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10, -1},
|
||||
{1, 10, 2, 8, 7, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{4, 9, 1, 4, 1, 7, 7, 1, 3, -1, -1, -1, -1, -1, -1, -1},
|
||||
{4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1, -1, -1, -1, -1},
|
||||
{4, 0, 3, 7, 4, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{4, 8, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{9, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{3, 0, 9, 3, 9, 11, 11, 9, 10, -1, -1, -1, -1, -1, -1, -1},
|
||||
{0, 1, 10, 0, 10, 8, 8, 10, 11, -1, -1, -1, -1, -1, -1, -1},
|
||||
{3, 1, 10, 11, 3, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{1, 2, 11, 1, 11, 9, 9, 11, 8, -1, -1, -1, -1, -1, -1, -1},
|
||||
{3, 0, 9, 3, 9, 11, 1, 2, 9, 2, 11, 9, -1, -1, -1, -1},
|
||||
{0, 2, 11, 8, 0, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{3, 2, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{2, 3, 8, 2, 8, 10, 10, 8, 9, -1, -1, -1, -1, -1, -1, -1},
|
||||
{9, 10, 2, 0, 9, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8, -1, -1, -1, -1},
|
||||
{1, 10, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{1, 3, 8, 9, 1, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{0, 9, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{0, 3, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The local offsets of the 8 corners of a single voxel cube.
|
||||
/// </summary>
|
||||
public static readonly Vector3[] CornerOffsets = new Vector3[8] {
|
||||
new Vector3(0, 0, 0), new Vector3(1, 0, 0), new Vector3(1, 0, 1), new Vector3(0, 0, 1),
|
||||
new Vector3(0, 1, 0), new Vector3(1, 1, 0), new Vector3(1, 1, 1), new Vector3(0, 1, 1)
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Defines which two corners make up each of the 12 edges.
|
||||
/// </summary>
|
||||
public static readonly int[,] EdgeConnections = new int[12, 2] {
|
||||
{0,1}, {1,2}, {2,3}, {3,0}, // Bottom face edges
|
||||
{4,5}, {5,6}, {6,7}, {7,4}, // Top face edges
|
||||
{0,4}, {1,5}, {2,6}, {3,7} // Vertical pillars
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Maps block IDs to vertex colors for the generated mesh. You can customize this to match your BiomePalette or block types!
|
||||
/// </summary>
|
||||
/// <param name="blockID"></param>
|
||||
/// <returns></returns>
|
||||
private static Color GetBlockColor(byte blockID)
|
||||
{
|
||||
// These map your BiomePalette selections to actual visual colors!
|
||||
if (blockID == BlockRegistry.SAND) return new Color(0.8f, 0.7f, 0.4f);
|
||||
if (blockID == BlockRegistry.SNOW) return new Color(0.9f, 0.9f, 0.9f);
|
||||
if (blockID == BlockRegistry.STONE) return new Color(0.4f, 0.4f, 0.4f);
|
||||
if (blockID == BlockRegistry.WASTELAND_DIRT) return new Color(0.35f, 0.25f, 0.25f);
|
||||
if (blockID == BlockRegistry.ASPHALT) return new Color(0.15f, 0.15f, 0.15f);
|
||||
|
||||
// Grass variations
|
||||
if (blockID == BlockRegistry.GRASS_JUNGLE) return new Color(0.1f, 0.4f, 0.1f);
|
||||
if (blockID == BlockRegistry.GRASS_PARADISE) return new Color(0.4f, 0.8f, 0.4f);
|
||||
if (blockID == BlockRegistry.GRASS_TROPICAL) return new Color(0.2f, 0.6f, 0.2f);
|
||||
|
||||
// Default Dirt
|
||||
return new Color(0.4f, 0.3f, 0.15f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A deterministic, integer-based key for our Dictionary welder.
|
||||
/// </summary>
|
||||
private struct EdgeKey : IEquatable<EdgeKey>
|
||||
{
|
||||
public Vector3I P1, P2;
|
||||
public EdgeKey(Vector3I p1, Vector3I p2)
|
||||
{
|
||||
// Always order them from lowest to highest so neighboring chunks generate the exact same key!
|
||||
if (p1.X < p2.X || (p1.X == p2.X && p1.Y < p2.Y) || (p1.X == p2.X && p1.Y == p2.Y && p1.Z < p2.Z)) {
|
||||
P1 = p1; P2 = p2;
|
||||
} else {
|
||||
P1 = p2; P2 = p1;
|
||||
}
|
||||
}
|
||||
public bool Equals(EdgeKey other) => P1.Equals(other.P1) && P2.Equals(other.P2);
|
||||
public override int GetHashCode() => HashCode.Combine(P1, P2);
|
||||
}
|
||||
|
||||
public static Vector3 VertexInterp(float isolevel, Vector3 p1, Vector3 p2, float valp1, float valp2)
|
||||
{
|
||||
if (Mathf.Abs(isolevel - valp1) < 0.00001f) return p1;
|
||||
if (Mathf.Abs(isolevel - valp2) < 0.00001f) return p2;
|
||||
if (Mathf.Abs(valp1 - valp2) < 0.00001f) return p1;
|
||||
|
||||
float mu = (isolevel - valp1) / (valp2 - valp1);
|
||||
|
||||
return new Vector3(
|
||||
p1.X + mu * (p2.X - p1.X),
|
||||
p1.Y + mu * (p2.Y - p1.Y),
|
||||
p1.Z + mu * (p2.Z - p1.Z)
|
||||
);
|
||||
}
|
||||
/// <summary>
|
||||
/// Calculates the exact 3D slope (gradient) of the terrain at a specific grid coordinate.
|
||||
/// </summary>
|
||||
public static Vector3 GetDensityGradient(float[,,] field, int x, int y, int z)
|
||||
{
|
||||
// Clamp to valid array indices so we don't crash when checking the chunk borders
|
||||
int x0 = Math.Max(0, x - 1), x1 = Math.Min(field.GetLength(0) - 1, x + 1);
|
||||
int y0 = Math.Max(0, y - 1), y1 = Math.Min(field.GetLength(1) - 1, y + 1);
|
||||
int z0 = Math.Max(0, z - 1), z1 = Math.Min(field.GetLength(2) - 1, z + 1);
|
||||
|
||||
// Calculate the difference in density across the X, Y, and Z axes
|
||||
float gx = field[x1, y, z] - field[x0, y, z];
|
||||
float gy = field[x, y1, z] - field[x, y0, z];
|
||||
float gz = field[x, y, z1] - field[x, y, z0];
|
||||
|
||||
return new Vector3(gx, gy, gz).Normalized();
|
||||
}
|
||||
|
||||
public static ArrayMesh GenerateMesh(float[,,] scalarField, byte[,,] blockIDs, float isolevel = 0.0f, float voxelScale = 1.0f)
|
||||
{
|
||||
var st = new SurfaceTool();
|
||||
st.Begin(Mesh.PrimitiveType.Triangles);
|
||||
st.SetSmoothGroup(1);
|
||||
|
||||
int width = scalarField.GetLength(0) - 1;
|
||||
int height = scalarField.GetLength(1) - 1;
|
||||
int depth = scalarField.GetLength(2) - 1;
|
||||
|
||||
float[] cornerDensities = new float[8];
|
||||
int[] edgeVertexIndices = new int[12]; // We now store the integer index, not the raw Vector3!
|
||||
|
||||
// The bulletproof integer welder
|
||||
Dictionary<EdgeKey, int> vertexCache = new Dictionary<EdgeKey, int>();
|
||||
int currentIndex = 0;
|
||||
int totalIndicesDrawn = 0; // For our diagnostic ratio
|
||||
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
for (int z = 0; z < depth; z++)
|
||||
{
|
||||
cornerDensities[0] = scalarField[x, y, z];
|
||||
cornerDensities[1] = scalarField[x + 1, y, z];
|
||||
cornerDensities[2] = scalarField[x + 1, y, z + 1];
|
||||
cornerDensities[3] = scalarField[x, y, z + 1];
|
||||
cornerDensities[4] = scalarField[x, y + 1, z];
|
||||
cornerDensities[5] = scalarField[x + 1, y + 1, z];
|
||||
cornerDensities[6] = scalarField[x + 1, y + 1, z + 1];
|
||||
cornerDensities[7] = scalarField[x, y + 1, z + 1];
|
||||
|
||||
int cubeIndex = 0;
|
||||
if (cornerDensities[0] < isolevel) cubeIndex |= 1;
|
||||
if (cornerDensities[1] < isolevel) cubeIndex |= 2;
|
||||
if (cornerDensities[2] < isolevel) cubeIndex |= 4;
|
||||
if (cornerDensities[3] < isolevel) cubeIndex |= 8;
|
||||
if (cornerDensities[4] < isolevel) cubeIndex |= 16;
|
||||
if (cornerDensities[5] < isolevel) cubeIndex |= 32;
|
||||
if (cornerDensities[6] < isolevel) cubeIndex |= 64;
|
||||
if (cornerDensities[7] < isolevel) cubeIndex |= 128;
|
||||
|
||||
if (EdgeTable[cubeIndex] == 0) continue;
|
||||
|
||||
Vector3 basePos = new Vector3(x, y, z) * voxelScale;
|
||||
|
||||
// Calculate the 12 edges
|
||||
for (int i = 0; i < 12; i++)
|
||||
{
|
||||
if ((EdgeTable[cubeIndex] & (1 << i)) != 0)
|
||||
{
|
||||
int cornerA = EdgeConnections[i, 0];
|
||||
int cornerB = EdgeConnections[i, 1];
|
||||
|
||||
// Create the flawless integer key
|
||||
Vector3I gridPosA = new Vector3I(x + (int)CornerOffsets[cornerA].X, y + (int)CornerOffsets[cornerA].Y, z + (int)CornerOffsets[cornerA].Z);
|
||||
Vector3I gridPosB = new Vector3I(x + (int)CornerOffsets[cornerB].X, y + (int)CornerOffsets[cornerB].Y, z + (int)CornerOffsets[cornerB].Z);
|
||||
EdgeKey key = new EdgeKey(gridPosA, gridPosB);
|
||||
|
||||
// If this edge hasn't been drawn by a neighbor yet, calculate it and cache it!
|
||||
if (!vertexCache.TryGetValue(key, out int vertIdx))
|
||||
{
|
||||
// 1. Calculate precise position
|
||||
Vector3 exactPos = basePos + VertexInterp(isolevel, CornerOffsets[cornerA] * voxelScale, CornerOffsets[cornerB] * voxelScale, cornerDensities[cornerA], cornerDensities[cornerB]);
|
||||
|
||||
// 2. ANALYTICAL NORMALS: Calculate the exact 3D slope at both corners
|
||||
Vector3 gradA = GetDensityGradient(scalarField, x + (int)CornerOffsets[cornerA].X, y + (int)CornerOffsets[cornerA].Y, z + (int)CornerOffsets[cornerA].Z);
|
||||
Vector3 gradB = GetDensityGradient(scalarField, x + (int)CornerOffsets[cornerB].X, y + (int)CornerOffsets[cornerB].Y, z + (int)CornerOffsets[cornerB].Z);
|
||||
|
||||
// 3. Interpolate the normal using the exact same fraction used for the position
|
||||
float valA = cornerDensities[cornerA];
|
||||
float valB = cornerDensities[cornerB];
|
||||
float mu = (isolevel - valA) / (valB - valA);
|
||||
Vector3 exactNormal = (gradA + mu * (gradB - gradA)).Normalized();
|
||||
|
||||
// 4. THE COLOR FIX: Grab the BlockID from CornerA and paint the vertex!
|
||||
// (We just reuse the gridPosA we already declared above the if-statement!)
|
||||
byte blockID = blockIDs[gridPosA.X, gridPosA.Y, gridPosA.Z];
|
||||
|
||||
st.SetColor(GetBlockColor(blockID));
|
||||
|
||||
// 5. Feed the perfect normal AND the position to Godot!
|
||||
// (SetColor and SetNormal MUST be called immediately before AddVertex)
|
||||
st.SetNormal(exactNormal);
|
||||
st.AddVertex(exactPos);
|
||||
|
||||
// 6. Cache it so neighbors don't redraw it!
|
||||
vertIdx = currentIndex++;
|
||||
vertexCache[key] = vertIdx;
|
||||
}
|
||||
|
||||
// Save the index for this specific cube to use in the TriTable
|
||||
edgeVertexIndices[i] = vertIdx;
|
||||
}
|
||||
}
|
||||
|
||||
// Connect the cached indices!
|
||||
for (int i = 0; TriTable[cubeIndex, i] != -1; i += 3)
|
||||
{
|
||||
st.AddIndex(edgeVertexIndices[TriTable[cubeIndex, i + 2]]);
|
||||
st.AddIndex(edgeVertexIndices[TriTable[cubeIndex, i + 1]]);
|
||||
st.AddIndex(edgeVertexIndices[TriTable[cubeIndex, i]]);
|
||||
totalIndicesDrawn += 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Diagnostic Print
|
||||
// GD.Print($"[MarchingCubes] Vertices: {currentIndex}, Indices: {totalIndicesDrawn}, Ratio: {(float)totalIndicesDrawn / Math.Max(1, currentIndex):F2}");
|
||||
|
||||
return st.Commit();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
1
Core/Scripts/MarchingCubes.cs.uid
Normal file
1
Core/Scripts/MarchingCubes.cs.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://4p2co3ctt0is
|
||||
46
Core/Scripts/README.md
Normal file
46
Core/Scripts/README.md
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# 📜 Core Scripts Directory - Isla Apocalypse
|
||||
|
||||
## Overview
|
||||
The `/Core/Scripts` directory is the dedicated code repository for all pure C# data structures, universal enumerations, and static mathematical utilities. Everything in this folder is compiled into the shared assembly used by both the authoritative `/Server` and the rendering `/Client`.
|
||||
|
||||
**Core Philosophy: "Pure Data, Zero State."**
|
||||
Scripts in this folder define *what* things are and *how* to calculate them, but they never remember *current* game events. They do not track who is online, what chunks are loaded, or what time of day it is. They are stateless, universal blueprints.
|
||||
|
||||
---
|
||||
|
||||
## 🧩 Current Systems (Phase 2)
|
||||
|
||||
### 1. The Voxel Palette System
|
||||
* **`BlockData.cs`**: The fundamental `struct` defining the properties of a single voxel (ID, Name, IsSolid, BaseColor).
|
||||
* **`BlockRegistry.cs`**: The static master dictionary. Contains the hardcoded IDs for every block in the game (Air, Bedrock, Dirt, Asphalt) and provides a safe lookup method (`GetBlock`) for both the mesher and the physics engine.
|
||||
* **`BiomePalette.cs`**: The translation layer between 2D and 3D. Contains the logic that dictates the vertical stacking of blocks based on biome and depth (e.g., ensuring mountains have stone beneath snow, and roads are topped with asphalt).
|
||||
|
||||
### 2. Universal Identifiers
|
||||
* **`Enums.cs`**: The master repository for global enums (`Biome`, `TownTier`, `MapHalf`). By keeping these here, the offline Map Generator, the Server, and the Client are guaranteed to use the exact same integer values for biomes and POIs.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Future Roadmap & Planned Additions (Phase 2 & 3)
|
||||
|
||||
As we build the 3D Chunk Manager and multiplayer networking, this folder will expand to include:
|
||||
|
||||
### 3. The Blueprint Parser
|
||||
* **`MapDataParser.cs`** *(Next Step)*: A static utility to open and decode the `MapData_Seed_[X].dat` binary file into usable C# arrays for the Server to ingest.
|
||||
|
||||
### 4. Chunk Data Structures
|
||||
* **`ChunkData.cs`**: The raw 3D array (`byte[,,]`) that holds the voxel IDs for a specific 16x16x64 area.
|
||||
* *Note:* This structure will include the `IsPlayerProtected` boolean flag to support the "Land Claim" delta-backup system, preventing player bases from being wiped out by chunk corruption.
|
||||
|
||||
### 5. Math & Coordinate Utilities
|
||||
* **`VoxelMath.cs`**: Static helper functions for translating massive 3D World Coordinates into local Chunk Coordinates (e.g., finding out which specific chunk file to load when a player walks to X: 5000, Z: -200).
|
||||
|
||||
### 6. Network Packet Definitions
|
||||
* Structs that define the exact byte layout of multiplayer messages (e.g., `PlayerDigPacket`, `ChatPacket`) to ensure precise Server/Client synchronization.
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ Directory Rules & Best Practices
|
||||
|
||||
1. **No Godot Node Inheritance:** Scripts here should rarely, if ever, inherit from `Node` or `Node3D`. They are pure C# classes, structs, or static utilities.
|
||||
2. **No `_Process` or `_Ready`:** Because these are not attached to active objects in the game world, they do not use Godot's frame-by-frame loop functions.
|
||||
3. **Strictly Independent:** A script in `/Core/Scripts` cannot reference anything in `/Client` or `/Server`. The dependency flows one way: the Client and Server look *in* to the Core; the Core never looks *out*.
|
||||
Loading…
Add table
Add a link
Reference in a new issue