#version 460 layout(local_size_x = 4, local_size_y = 4, local_size_z = 4) in; const int CHUNK_SIZE = 16; const int SCRATCH_SIZE = CHUNK_SIZE + 2; const int SCRATCH_AREA = SCRATCH_SIZE * SCRATCH_SIZE; const int SCRATCH_VOXEL_COUNT = SCRATCH_SIZE * SCRATCH_SIZE * SCRATCH_SIZE; const int STRUCTURE_CELL_SIZE = 16; const int REPLACE_AIR_ONLY = 1; const int REPLACE_FOLIAGE = 2; const int REQUIRE_FLAT_GROUND = 4; const int HAS_FOUNDATION = 8; layout(push_constant) uniform ChunkInfo { ivec3 chunkPos; int slot; uint faceOffset; uint voxelTypeCount; uint biomeCount; uint structureCount; uint worldSeed; }; struct Biome { uint surfaceBlock; uint dirtBlock; uint stoneBlock; uint grassFoliage; uint[4] flowers; uint[4] tallBlocks; uint[4] otherFoliage; }; struct Structure { uint sizeX; uint sizeY; uint sizeZ; uint blockOffset; uint spawnSpacing; uint spawnChance; uint allowedBiome; uint groundBlock; uint flags; }; struct StructureVoxelResult { uint block; uint flags; }; layout(std430, binding = 0) buffer VoxelData { uint voxels[]; }; layout(std430, set = 1, binding = 0) readonly buffer BiomeUniform { Biome biomes[]; } BiomeReg; layout(std430, set = 2, binding = 0) readonly buffer StructureRegistry { Structure structures[]; } structureReg; layout(std430, set = 2, binding = 1) readonly buffer StructureBlocks { uint blocks[]; } structureBlocks; uint hash21(uvec2 p) { p = p * 1664525u + 1013904223u; p.x += p.y * 1664525u; p.y += p.x * 1664525u; return p.x ^ (p.x >> 16); } float random3to1(vec3 st) { return fract(sin(dot(st, vec3(127.1, 311.7, 74.7))) * 43758.5453123); } float interpolate(float a, float b, float c, float d, float x) { float p = (d - c) - (a - b); return x * (x * (x * p + ((a - b) - p)) + (c - a)) + b; } float sampleX(vec3 at) { float floored = floor(at.x); return interpolate( random3to1(vec3(floored - 1.0, at.yz)), random3to1(vec3(floored, at.yz)), random3to1(vec3(floored + 1.0, at.yz)), random3to1(vec3(floored + 2.0, at.yz)), at.x - floored) * 0.5 + 0.25; } float sampleY(vec3 at) { float floored = floor(at.y); return interpolate( sampleX(vec3(at.x, floored - 1.0, at.z)), sampleX(vec3(at.x, floored, at.z)), sampleX(vec3(at.x, floored + 1.0, at.z)), sampleX(vec3(at.x, floored + 2.0, at.z)), at.y - floored); } float cubicNoise(vec3 at) { float floored = floor(at.z); return interpolate( sampleY(vec3(at.xy, floored - 1.0)), sampleY(vec3(at.xy, floored)), sampleY(vec3(at.xy, floored + 1.0)), sampleY(vec3(at.xy, floored + 2.0)), at.z - floored); } float rand(vec2 co) { return fract(sin(dot(co.xy, vec2(12.9898, 78.233))) * 43758.5453123); } float valueNoise(vec2 st) { vec2 i = floor(st); vec2 f = fract(st); float a = rand(i); float b = rand(i + vec2(1.0, 0.0)); float c = rand(i + vec2(0.0, 1.0)); float d = rand(i + vec2(1.0, 1.0)); vec2 u = f * f * (3.0 - 2.0 * f); return mix(a, b, u.x) + (c - a) * u.y * (1.0 - u.x) + (d - b) * u.x * u.y; } uint pcg_hash(uint seed) { uint state = seed * 747796405u + 289133645u; uint word = ((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u; return (word >> 22u) ^ word; } uint randomRangeUint(uint seed, uint minVal, uint maxVal) { uint range = maxVal - minVal + 1u; return minVal + (pcg_hash(seed) % range); } uint GetBiome(vec3 worldPos, uint MaxBiomes) { if (MaxBiomes == 0u) return 0u; vec3 X = vec3(worldPos); X = mat3( 0.788675134594813, -0.211324865405187, -0.577350269189626, -0.211324865405187, 0.788675134594813, -0.577350269189626, 0.577350269189626, 0.577350269189626, 0.577350269189626) * X; float n = clamp(cubicNoise(X), 0.0, 0.9999); uint Biome = uint(floor(n * float(MaxBiomes))); return min(Biome, MaxBiomes - 1u); } bool isCave(vec3 worldPos) { vec3 X = vec3(worldPos); X = mat3( 0.788675134594813, -0.211324865405187, -0.577350269189626, -0.211324865405187, 0.788675134594813, -0.577350269189626, 0.577350269189626, 0.577350269189626, 0.577350269189626) * X; float n = cubicNoise(X); return n > 0.5; } bool shouldSpawn(uint seed, Structure s) { return pcg_hash(seed) % 1000u < s.spawnChance; } bool hasFlag(uint flags, uint flag) { return (flags & flag) != 0u; } int floorDiv(int value, int divisor) { int q = value / divisor; int r = value - q * divisor; if (r != 0 && ((r < 0) != (divisor < 0))) { q -= 1; } return q; } ivec2 getStructureCell(ivec2 worldXZ, int spacing) { return ivec2( floorDiv(worldXZ.x, spacing), floorDiv(worldXZ.y, spacing) ); } float getPrimarySurfaceHeight(int worldX, int worldY, int worldZ) { float YZone = floor(float(worldY) / 150.0) * 150.0; return valueNoise(vec2(float(worldX) + YZone * 2.0, float(worldZ) + YZone * 2.0) * 0.005) * 100.0 + valueNoise(vec2(float(worldX) + YZone * 2.0, float(worldZ) + YZone * 2.0) * 0.01) * 10.0 + valueNoise(vec2(float(worldX) + YZone * 2.0, float(worldZ) + YZone * 2.0) * 0.1) * 5.0 + valueNoise(vec2(float(worldX) + YZone * 2.0, float(worldZ) + YZone * 2.0)) * 0.5 + YZone; } float getSecondarySurfaceHeight(int worldX, int worldY, int worldZ) { float YZone2 = floor(float(worldY) / 75.0) * 75.0; return valueNoise(vec2(float(worldX) + YZone2 * 2.0, float(worldZ) + YZone2 * 2.0) * 0.005) * 50.0 + valueNoise(vec2(float(worldX) + YZone2 * 2.0, float(worldZ) + YZone2 * 2.0) * 0.01) * 10.0 + valueNoise(vec2(float(worldX) + YZone2 * 2.0, float(worldZ) + YZone2 * 2.0) * 0.1) * 5.0 + valueNoise(vec2(float(worldX) + YZone2 * 2.0, float(worldZ) + YZone2 * 2.0)) * 0.5 + YZone2; } float getSurfaceHeight(int worldX, int worldZ, int referenceY) { float primary = getPrimarySurfaceHeight(worldX, referenceY, worldZ); float secondary = getSecondarySurfaceHeight(worldX, referenceY, worldZ); float primaryDist = abs(primary - float(referenceY)); float secondaryDist = abs(secondary - float(referenceY)); return primaryDist <= secondaryDist ? primary : secondary; } bool cellSpawnsStructure(uint seed, Structure s) { if (s.spawnChance == 0u) { return false; } return pcg_hash(seed) % 1000u < s.spawnChance; } uint chooseStructure(uint seed, uint biome) { if (structureCount == 0u) { return 0u; } uint start = pcg_hash(seed ^ 0xA53A9D31u) % structureCount; for (uint i = 0u; i < structureCount; i++) { uint id = (start + i) % structureCount; Structure s = structureReg.structures[id]; if (s.allowedBiome == 0u || s.allowedBiome == biome + 1u) { return id; } } return start; } ivec2 getStructureAnchorXZ(ivec2 cell, uint seed, uint spacing) { int structureSpacing = int(max(spacing, 1u)); uint jitterX = randomRangeUint(seed ^ 0xB5297A4Du, 0u, uint(structureSpacing - 1)); uint jitterZ = randomRangeUint(seed ^ 0x68E31DA4u, 0u, uint(structureSpacing - 1)); return cell * structureSpacing + ivec2(int(jitterX), int(jitterZ)); } uint getStructureBlock(uint structureId, ivec3 localPos) { Structure s = structureReg.structures[structureId]; if (localPos.x < 0 || localPos.x >= int(s.sizeX)) return 0u; if (localPos.y < 0 || localPos.y >= int(s.sizeY)) return 0u; if (localPos.z < 0 || localPos.z >= int(s.sizeZ)) return 0u; uint index = uint(localPos.x) * s.sizeY * s.sizeZ + uint(localPos.y) * s.sizeZ + uint(localPos.z); return structureBlocks.blocks[s.blockOffset + index]; } bool isGroundFlatEnough(ivec2 anchorXZ, uint structureId, int referenceY) { Structure s = structureReg.structures[structureId]; float h0 = getSurfaceHeight(anchorXZ.x, anchorXZ.y, referenceY); float h1 = getSurfaceHeight(anchorXZ.x + int(s.sizeX) - 1, anchorXZ.y, referenceY); float h2 = getSurfaceHeight(anchorXZ.x, anchorXZ.y + int(s.sizeZ) - 1, referenceY); float h3 = getSurfaceHeight(anchorXZ.x + int(s.sizeX) - 1, anchorXZ.y + int(s.sizeZ) - 1, referenceY); float minH = min(min(h0, h1), min(h2, h3)); float maxH = max(max(h0, h1), max(h2, h3)); return maxH - minH <= 2.0; } ivec3 getStructureCell3D(ivec3 worldPos, int spacing) { return ivec3( floorDiv(worldPos.x, spacing), floorDiv(worldPos.y, 75), floorDiv(worldPos.z, spacing) ); } uint hash31(ivec3 p) { uvec3 v = uvec3(p) * uvec3(1664525u, 22695477u, 1103515245u) + uvec3(1013904223u); v.x += v.y * v.z; v.y += v.z * v.x; v.z += v.x * v.y; return v.x ^ v.y ^ v.z; } uint getBaseTerrainVoxelAt(ivec3 worldPos, uint biome) { Biome currentBiome = BiomeReg.biomes[biome]; float YZone = floor(float(worldPos.y) / 150.0) * 150.0; float SurfaceHeight = valueNoise(vec2(float(worldPos.x) + YZone * 2.0, float(worldPos.z) + YZone * 2.0) * 0.005) * 100.0 + valueNoise(vec2(float(worldPos.x) + YZone * 2.0, float(worldPos.z) + YZone * 2.0) * 0.01) * 10.0 + valueNoise(vec2(float(worldPos.x) + YZone * 2.0, float(worldPos.z) + YZone * 2.0) * 0.1) * 5.0 + valueNoise(vec2(float(worldPos.x) + YZone * 2.0, float(worldPos.z) + YZone * 2.0)) * 0.5 + YZone; float IslandUnderneathHeight = valueNoise(vec2(float(worldPos.x) + YZone * 2.0, float(worldPos.z) + YZone * 2.0) * 0.005) * 100.0 + valueNoise(vec2(float(worldPos.x) + YZone * 2.0, float(worldPos.z) + YZone * 2.0) * 0.1) * 20.0 + valueNoise(vec2(float(worldPos.x) + YZone * 2.0, float(worldPos.z) + YZone * 2.0) * 0.1) * 5.0 + valueNoise(vec2(float(worldPos.x) + YZone * 2.0, float(worldPos.z) + YZone * 2.0)) * 0.5 - 15.0 + YZone; float grassNoise = valueNoise(vec2(float(worldPos.z) * 0.5 - YZone * 2.0, float(worldPos.x) * 0.5 - YZone * 2.0)); uint randomBlockVal = randomRangeUint(uint(worldPos.x) * 73856093u ^ uint(worldPos.z) * 19349663u, 0u, 3u); uint voxelType = 0u; if (float(worldPos.y) <= SurfaceHeight && float(worldPos.y) >= IslandUnderneathHeight) { float depthBelowSurface = SurfaceHeight - float(worldPos.y); if (depthBelowSurface < 0.5) { if (grassNoise < 0.65) voxelType = currentBiome.grassFoliage; else if (grassNoise < 0.75) voxelType = currentBiome.flowers[randomBlockVal]; else if (grassNoise < 0.85) voxelType = currentBiome.otherFoliage[randomBlockVal]; else voxelType = 0u; } else if (depthBelowSurface < 2.5) { voxelType = currentBiome.surfaceBlock; } else if (depthBelowSurface < 6.0) { voxelType = currentBiome.dirtBlock; } else { voxelType = currentBiome.stoneBlock; } } else { float YZone2 = floor(float(worldPos.y) / 75.0) * 75.0; float IslandUnderneathHeight2 = valueNoise(vec2(float(worldPos.x) - YZone2 * 2.0, float(worldPos.z) - YZone2 * 2.0) * 0.005) * 60.0 + valueNoise(vec2(float(worldPos.x) + YZone2 * 2.0, float(worldPos.z) + YZone2 * 2.0) * 0.1) * 20.0 + valueNoise(vec2(float(worldPos.x) + YZone2 * 2.0, float(worldPos.z) + YZone2 * 2.0) * 0.1) * 10.0 + valueNoise(vec2(float(worldPos.x) + YZone2 * 2.0, float(worldPos.z) + YZone2 * 2.0)) * 0.5 - 5.0 + YZone2; float height2 = valueNoise(vec2(float(worldPos.x) + YZone2 * 2.0, float(worldPos.z) + YZone2 * 2.0) * 0.005) * 50.0 + valueNoise(vec2(float(worldPos.x) + YZone2 * 2.0, float(worldPos.z) + YZone2 * 2.0) * 0.01) * 10.0 + valueNoise(vec2(float(worldPos.x) + YZone2 * 2.0, float(worldPos.z) + YZone2 * 2.0) * 0.1) * 5.0 + valueNoise(vec2(float(worldPos.x) + YZone2 * 2.0, float(worldPos.z) + YZone2 * 2.0)) * 0.5 + YZone2; if (float(worldPos.y) <= height2 && float(worldPos.y) >= IslandUnderneathHeight2) { float depthBelowSurface = height2 - float(worldPos.y); if (depthBelowSurface < 0.5) { if (grassNoise < 0.65) voxelType = currentBiome.grassFoliage; else if (grassNoise < 0.75) voxelType = currentBiome.flowers[randomBlockVal]; else if (grassNoise < 0.85) voxelType = currentBiome.otherFoliage[randomBlockVal]; else voxelType = 0u; } else if (depthBelowSurface < 1.5) { voxelType = currentBiome.surfaceBlock; } else if (depthBelowSurface < 6.0) { voxelType = currentBiome.dirtBlock; } else { voxelType = currentBiome.stoneBlock; } } } if (voxelType != 0u && isCave(vec3(worldPos) * 0.05)) { voxelType = 0u; } return voxelType; } bool isValidStructureAnchor(ivec3 origin, uint structureId, uint biome) { Structure s = structureReg.structures[structureId]; ivec3 groundPos = origin + ivec3(0, -1, 0); ivec3 basePos = origin; uint groundVoxel = getBaseTerrainVoxelAt(groundPos, biome); uint baseVoxel = getBaseTerrainVoxelAt(basePos, biome); if (groundVoxel == 0u) { return false; } if (s.groundBlock != 0u && groundVoxel != s.groundBlock) { return false; } if (baseVoxel != 0u) { return false; } if (isCave(vec3(groundPos) * 0.05)) { return false; } return true; } int getStructureReferenceY(int worldY) { return floorDiv(worldY, 75) * 75; } bool isStructureFootprintSupported(ivec3 origin, uint structureId, uint biome) { Structure s = structureReg.structures[structureId]; int supportedCorners = 0; ivec3 p0 = origin + ivec3(0, -1, 0); ivec3 p1 = origin + ivec3(int(s.sizeX) - 1, -1, 0); ivec3 p2 = origin + ivec3(0, -1, int(s.sizeZ) - 1); ivec3 p3 = origin + ivec3(int(s.sizeX) - 1, -1, int(s.sizeZ) - 1); if (getBaseTerrainVoxelAt(p0, biome) != 0u) supportedCorners++; if (getBaseTerrainVoxelAt(p1, biome) != 0u) supportedCorners++; if (getBaseTerrainVoxelAt(p2, biome) != 0u) supportedCorners++; if (getBaseTerrainVoxelAt(p3, biome) != 0u) supportedCorners++; return supportedCorners == 4; } StructureVoxelResult getStructureVoxelAt(ivec3 worldPos, uint biome) { StructureVoxelResult result; result.block = 0u; result.flags = 0u; if (structureCount == 0u) { return result; } for (uint structureId = 0u; structureId < structureCount; structureId++) { Structure s = structureReg.structures[structureId]; if (s.allowedBiome != 0u && s.allowedBiome != biome + 1u) { continue; } int spacing = int(max(s.spawnSpacing, 1u)); ivec3 baseCell = getStructureCell3D(worldPos, spacing); for (int ox = -1; ox <= 1; ox++) { for (int oy = -1; oy <= 1; oy++) { for (int oz = -1; oz <= 1; oz++) { ivec3 cell = baseCell + ivec3(ox, oy, oz); uint seed = hash31(cell + ivec3(0, int(structureId) * 97, 0)) ^ worldSeed; if (!cellSpawnsStructure(seed, s)) { continue; } ivec2 anchorXZ = getStructureAnchorXZ(cell.xz, seed, s.spawnSpacing); // Match getStructureBlock's uncentered footprint before sampling terrain. ivec2 localXZ = worldPos.xz - anchorXZ; if (localXZ.x < 0 || localXZ.x >= int(s.sizeX) || localXZ.y < 0 || localXZ.y >= int(s.sizeZ)) { continue; } int referenceY = cell.y * 75; float anchorHeight = getSurfaceHeight(int(anchorXZ.x - s.sizeX/2), int(anchorXZ.y - s.sizeZ/2), referenceY); int anchorY = int(floor(anchorHeight)) + 1; ivec3 origin = ivec3(anchorXZ.x, anchorY, anchorXZ.y); if (!isValidStructureAnchor(origin, structureId, biome)) { continue; } ivec3 local = worldPos - origin; uint block = getStructureBlock(structureId, local); if (block != 0u) { result.block = block; result.flags = s.flags; return result; } } } } } return result; } void main() { if (any(greaterThanEqual(gl_GlobalInvocationID.xyz, uvec3(SCRATCH_SIZE)))) { return; } ivec3 localPos = ivec3(gl_GlobalInvocationID.xyz) - ivec3(1); ivec3 worldPos = chunkPos * CHUNK_SIZE + localPos; uint biome = GetBiome(vec3(worldPos.z, worldPos.y, worldPos.x) * 0.005, biomeCount); Biome currentBiome = BiomeReg.biomes[biome]; float YZone = floor(worldPos.y/150.0)*150.0; float SurfaceHeight = valueNoise(vec2(worldPos.x + YZone * 2, worldPos.z + YZone * 2) * 0.005) * 100 + valueNoise(vec2(worldPos.x + YZone * 2, worldPos.z + YZone * 2) * 0.01) * 10 + valueNoise(vec2(worldPos.x + YZone * 2, worldPos.z + YZone * 2) * 0.1) * 5 + valueNoise(vec2(worldPos.x + YZone * 2, worldPos.z + YZone * 2)) * 0.5 + YZone; uint RandomBlockVal = randomRangeUint(uint(worldPos.x) * 73856093u ^ uint(worldPos.z) * 19349663u,0u ,3u); float IslandUnderneathHeight = valueNoise(vec2(worldPos.x + YZone * 2, worldPos.z + YZone * 2) * 0.005) * 100 + valueNoise(vec2(worldPos.x + YZone * 2, worldPos.z + YZone * 2) * 0.1) * 20 + valueNoise(vec2(worldPos.x + YZone * 2, worldPos.z + YZone * 2) * 0.1) * 5 + valueNoise(vec2(worldPos.x + YZone * 2, worldPos.z + YZone * 2)) * 0.5 - 15 + YZone; float grassNoise = valueNoise(vec2(worldPos.z * 0.5 - YZone * 2, worldPos.x * 0.5 - YZone * 2)); uint voxelType = 0u; if (float(worldPos.y) <= SurfaceHeight && float(worldPos.y) >= IslandUnderneathHeight) { float depthBelowSurface = SurfaceHeight - float(worldPos.y); if (depthBelowSurface < 0.5) { if(grassNoise < 0.65) voxelType = currentBiome.grassFoliage; // Grass foliage else if (grassNoise < 0.75) voxelType = currentBiome.flowers[RandomBlockVal]; else if (grassNoise < 0.85) voxelType = currentBiome.otherFoliage[RandomBlockVal]; else voxelType = 0u; }else if (depthBelowSurface < 2.5) { voxelType = currentBiome.surfaceBlock; // Grass } else if (depthBelowSurface < 6.0) { voxelType = currentBiome.dirtBlock; // Dirt(also stone rn) } else { voxelType = currentBiome.stoneBlock; // Stone } } else{ float YZone2 = floor(worldPos.y/75.0)*75.0; float IslandUnderneathHeight2 = valueNoise(vec2(worldPos.x - YZone2 * 2, worldPos.z -YZone2 * 2) * 0.005) * 60 + valueNoise(vec2(worldPos.x + YZone2 * 2, worldPos.z + YZone2 * 2) * 0.1) * 20 + valueNoise(vec2(worldPos.x + YZone2 * 2, worldPos.z + YZone2 * 2) * 0.1) * 10+ valueNoise(vec2(worldPos.x + YZone2 * 2, worldPos.z + YZone2 * 2)) * 0.5 - 5 + YZone2; float height2 = valueNoise(vec2(worldPos.x + YZone2 * 2, worldPos.z + YZone2 * 2) * 0.005) * 50 + valueNoise(vec2(worldPos.x + YZone2 * 2, worldPos.z + YZone2 * 2) * 0.01) * 10 + valueNoise(vec2(worldPos.x + YZone2 * 2, worldPos.z + YZone2 * 2) * 0.1) * 5+ valueNoise(vec2(worldPos.x + YZone2 * 2, worldPos.z + YZone2 * 2)) * 0.5 + YZone2; if (float(worldPos.y) <= height2 && float(worldPos.y) >= IslandUnderneathHeight2) { float depthBelowSurface = height2 - float(worldPos.y); if (depthBelowSurface < 0.5) { if (grassNoise < 0.65) voxelType = currentBiome.grassFoliage; // Grass foliage else if (grassNoise < 0.75) voxelType = currentBiome.flowers[RandomBlockVal]; else if (grassNoise < 0.85) voxelType = currentBiome.otherFoliage[RandomBlockVal]; else voxelType = 0u; } else if (depthBelowSurface < 1.5) { voxelType = currentBiome.surfaceBlock; // Grass } else if (depthBelowSurface < 6.0) { voxelType = currentBiome.dirtBlock; // Dirt(also stone rn) } else { voxelType = currentBiome.stoneBlock; // Stone } } } if (voxelType != 0u) { if (isCave(vec3(worldPos*0.05))) { voxelType = 0u; } } StructureVoxelResult structureVoxel = getStructureVoxelAt(worldPos, biome); if (structureVoxel.block != 0u) { if (hasFlag(structureVoxel.flags, uint(REPLACE_AIR_ONLY))) { if (voxelType == 0u) { voxelType = structureVoxel.block; } } else { voxelType = structureVoxel.block; } } uint index = uint(slot * SCRATCH_VOXEL_COUNT + (localPos.x + 1) * SCRATCH_AREA + (localPos.y + 1) * SCRATCH_SIZE + localPos.z + 1); voxels[index] = voxelType; }