From d1abde919d85ce943aec491a7550aaa0519dff9f Mon Sep 17 00:00:00 2001 From: Halbear Date: Sun, 20 Sep 2026 22:43:42 +0100 Subject: [PATCH] structures, also they're a little broken, they need to check for neighbouring YZones as well --- .../meshGenerationShared.glsl | 4 +- .../resetVoxelDrawShared.glsl | 6 +- .../voxelGenerationShared.glsl | 397 +++++++++++++++++- .../shaders/packed_scene_vertex.glsl | 3 +- .../EngineResources/shaders/water_frag.glsl | 51 --- .../EngineResources/shaders/water_vert.glsl | 46 -- .../EngineCore/Display/VulkanRenderer.java | 9 +- .../SharedVoxelTerrainResources.java | 81 +++- .../StructureFlags.java | 8 + .../VoxelChunkGenerator.java | 10 +- .../VoxelStructure.java | 4 + .../VoxelWorldManager.java | 119 +++++- 12 files changed, 604 insertions(+), 134 deletions(-) delete mode 100644 resources/EngineResources/shaders/water_frag.glsl delete mode 100644 resources/EngineResources/shaders/water_vert.glsl create mode 100644 src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Computing/VoxelTerrainGeneration/StructureFlags.java create mode 100644 src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Computing/VoxelTerrainGeneration/VoxelStructure.java diff --git a/resources/EngineResources/VoxelComputeShaders/meshGenerationShared.glsl b/resources/EngineResources/VoxelComputeShaders/meshGenerationShared.glsl index 844a691..beced9d 100644 --- a/resources/EngineResources/VoxelComputeShaders/meshGenerationShared.glsl +++ b/resources/EngineResources/VoxelComputeShaders/meshGenerationShared.glsl @@ -53,7 +53,9 @@ layout(push_constant) uniform ChunkInfo { uint faceOffset; uint voxelTypeCount; uint indirectCommandIndex; - uint padding0; + uint biomeCount; + uint structureCount; + uint worldSeed; }; uint flatten(ivec3 pos) { diff --git a/resources/EngineResources/VoxelComputeShaders/resetVoxelDrawShared.glsl b/resources/EngineResources/VoxelComputeShaders/resetVoxelDrawShared.glsl index dc84fbb..d3b34f9 100644 --- a/resources/EngineResources/VoxelComputeShaders/resetVoxelDrawShared.glsl +++ b/resources/EngineResources/VoxelComputeShaders/resetVoxelDrawShared.glsl @@ -21,9 +21,11 @@ layout(push_constant) uniform ChunkInfo { ivec3 chunkPos; int slot; uint faceOffset; - uint unused0; + uint voxelTypeCount; uint indirectCommandIndex; - uint padding0; + uint biomeCount; + uint structureCount; + uint worldSeed; }; void main() { diff --git a/resources/EngineResources/VoxelComputeShaders/voxelGenerationShared.glsl b/resources/EngineResources/VoxelComputeShaders/voxelGenerationShared.glsl index e091158..52a9b60 100644 --- a/resources/EngineResources/VoxelComputeShaders/voxelGenerationShared.glsl +++ b/resources/EngineResources/VoxelComputeShaders/voxelGenerationShared.glsl @@ -3,11 +3,23 @@ layout(local_size_x = 4, local_size_y = 4, local_size_z = 4) in; const int CHUNK_SIZE = 16; const int CHUNK_AREA = CHUNK_SIZE * CHUNK_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; -// A 1D storage buffer representing a flat 3D array of voxel IDs -layout(std430, binding = 0) buffer VoxelData { - uint voxels[]; +layout(push_constant) uniform ChunkInfo { + ivec3 chunkPos; + int slot; + uint faceOffset; + uint voxelTypeCount; + uint indirectCommandIndex; + uint biomeCount; + uint structureCount; + uint worldSeed; }; + struct Biome { uint surfaceBlock; uint dirtBlock; @@ -18,19 +30,45 @@ struct Biome { 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(push_constant) uniform ChunkInfo { - ivec3 chunkPos; - int slot; - uint faceOffset; - uint unused0; - uint indirectCommandIndex; - uint biomeCount; -}; +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); } @@ -127,6 +165,331 @@ bool isCave(vec3 worldPos) { 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 oz = -1; oz <= 1; oz++) { + ivec3 cell = baseCell + ivec3(ox, 0, 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); + + int referenceY = cell.y * 75; + float anchorHeight = getSurfaceHeight(anchorXZ.x, anchorXZ.y, 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() { ivec3 localPos = ivec3(gl_GlobalInvocationID.xyz); ivec3 worldPos = chunkPos * CHUNK_SIZE + localPos; @@ -202,6 +565,18 @@ void main() { } } + 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((localPos.x * CHUNK_AREA) + (localPos.y * CHUNK_SIZE) + localPos.z); voxels[index] = voxelType; } \ No newline at end of file diff --git a/resources/EngineResources/shaders/packed_scene_vertex.glsl b/resources/EngineResources/shaders/packed_scene_vertex.glsl index be818ae..d8e961f 100644 --- a/resources/EngineResources/shaders/packed_scene_vertex.glsl +++ b/resources/EngineResources/shaders/packed_scene_vertex.glsl @@ -110,7 +110,8 @@ void main() { faceId = min(faceId, 7u); vec3 localPos = vec3(voxelX, voxelY, voxelZ) + FACE_CORNERS[faceId][cornerIndex]; - vec3 worldPos = localPos + push_constants.ModelOffset.xyz * 16.0; + vec3 Offset = vec3(0,0,0); + vec3 worldPos = localPos + Offset + push_constants.ModelOffset.xyz * 16.0; vec4 worldPosVec4 = vec4(worldPos, 1.0); diff --git a/resources/EngineResources/shaders/water_frag.glsl b/resources/EngineResources/shaders/water_frag.glsl deleted file mode 100644 index 745b053..0000000 --- a/resources/EngineResources/shaders/water_frag.glsl +++ /dev/null @@ -1,51 +0,0 @@ -#version 460 core - -in vec3 FragPos; -in vec2 TexCoords; -in vec3 Normal; - -out vec4 FragColor; - -uniform vec3 cameraPos; -uniform vec3 lightPos; - -// Material constants -const vec3 waterShallowColor = vec3(0.0, 0.6, 0.7); -const vec3 waterDeepColor = vec3(0.05, 0.15, 0.3); -const vec3 sunColor = vec3(1.0, 0.95, 0.8); - -void main() { - // Normalize vectors - vec3 n = normalize(Normal); - vec3 viewDir = normalize(cameraPos - FragPos); - vec3 lightDir = normalize(lightPos - FragPos); - - // 1. Ambient Lighting - vec3 ambient = 0.3 * waterShallowColor; - - // 2. Diffuse Shading - float diff = max(dot(n, lightDir), 0.0); - vec3 diffuse = diff * sunColor * 0.4; - - // 3. Specular Highlights (Blinn-Phong) - vec3 halfwayDir = normalize(lightDir + viewDir); - float spec = pow(max(dot(n, halfwayDir), 0.0), 64.0); // High shininess for water glaze - vec3 specular = spec * sunColor * 0.8; - - // 4. Fresnel Approximation (Schlick's approximation) - // Water has a base reflectivity of roughly 0.02 at a perpendicular view angle - float F0 = 0.02; - float fresnel = F0 + (1.0 - F0) * pow(1.0 - max(dot(n, viewDir), 0.0), 5.0); - - // Mix deep and shallow water colors based on the normal angle - vec3 baseWaterColor = mix(waterShallowColor, waterDeepColor, max(dot(n, vec3(0.0, 1.0, 0.0)), 0.0)); - - // Combine lighting results - vec3 lightingResult = ambient + diffuse + specular; - - // Blend final look with Fresnel reflection dominance - vec3 finalColor = mix(baseWaterColor + specular, lightingResult + vec3(fresnel * 0.5), fresnel); - - // Output final color with subtle translucency opacity - FragColor = vec4(finalColor, 0.85); -} diff --git a/resources/EngineResources/shaders/water_vert.glsl b/resources/EngineResources/shaders/water_vert.glsl deleted file mode 100644 index ac3c0a1..0000000 --- a/resources/EngineResources/shaders/water_vert.glsl +++ /dev/null @@ -1,46 +0,0 @@ -#version 450 - -layout (location = 0) in vec3 aPos; -layout (location = 1) in vec2 aTexCoords; - -out vec3 FragPos; -out vec2 TexCoords; -out vec3 Normal; - -uniform mat4 model; -uniform mat4 view; -uniform mat4 projection; -uniform float uTime; - -// Wave configuration constants -const float AMPLITUDE = 0.15; -const float FREQUENCY = 1.5; -const float SPEED = 2.0; - -// Simple wave function that modifies elevation based on position and time -float calculateWave(vec3 pos, float time, vec2 direction) { - return AMPLITUDE * sin(dot(pos.xz, direction) * FREQUENCY + time * SPEED); -} - -// Derivative of the wave function to calculate accurate dynamic surface normals -vec3 calculateWaveNormal(vec3 pos, float time) { - float dx = AMPLITUDE * FREQUENCY * cos(pos.x * FREQUENCY + time * SPEED); - float dz = AMPLITUDE * FREQUENCY * cos(pos.z * FREQUENCY + time * SPEED); - return normalize(vec3(-dx, 1.0, -dz)); -} - -void main() { - vec3 displacedPos = aPos; - - // Combine two wave directions for a less predictable, more organic look - displacedPos.y += calculateWave(aPos, uTime, vec2(1.0, 0.0)); - displacedPos.y += calculateWave(aPos, uTime * 1.2, vec2(0.5, 0.8)); - - FragPos = vec3(model * vec4(displacedPos, 1.0)); - TexCoords = aTexCoords; - - // Pass transformed normals and position to the fragment shader - Normal = mat3(transpose(inverse(model))) * calculateWaveNormal(displacedPos, uTime); - - gl_Position = projection * view * model * vec4(displacedPos, 1.0); -} diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Display/VulkanRenderer.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Display/VulkanRenderer.java index a1b633a..66ca558 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/Display/VulkanRenderer.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Display/VulkanRenderer.java @@ -200,6 +200,7 @@ public class VulkanRenderer implements Renderer { int Index = materialsCache.GetPosition("VoxelTerrain"); int Index2 = materialsCache.GetPosition("Blueleaf"); int Index3 = materialsCache.GetPosition("Blueleaf2"); + Voxel grassVoxel = new Voxel(Index, 0, new Vector2i[]{positionArr[0][1], positionArr[0][3]}, new Vector2i[]{positionArr[1][0], positionArr[1][0]}, @@ -373,8 +374,14 @@ public class VulkanRenderer implements Renderer { Biome GreenBiome = new Biome(1,2,3,4,new int[]{5,6,7,4},new int[]{4,4,4,4},new int[]{4,18,16,17}); Biome Blueleaf = new Biome(8,9,3,10,new int[]{11,19,20,14},new int[]{5,6,7,8},new int[]{15,16,17,18}); - VoxelWorldManager.AddBiome(Blueleaf, "blueleaf"); VoxelWorldManager.AddBiome(GreenBiome, "greenBiome"); + VoxelWorldManager.AddBiome(Blueleaf, "blueleaf"); + + VoxelWorldManager.GenerateExampleTree(5,8,5,0,1,6,7,"green_tree", 15, 0.5f); + VoxelWorldManager.GenerateExampleTree(12,16,12,0,1,6,7,"green_tree_tall", 15, 0.5f); + VoxelWorldManager.GenerateExampleTree(7,12,7,1,8,6,20,"blue_tree", 30,0.25f); + + } public GuiRenderer GetGUIRenderer(){return GuiRender;} diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Computing/VoxelTerrainGeneration/SharedVoxelTerrainResources.java b/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Computing/VoxelTerrainGeneration/SharedVoxelTerrainResources.java index 24796f3..bb81fb0 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Computing/VoxelTerrainGeneration/SharedVoxelTerrainResources.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Computing/VoxelTerrainGeneration/SharedVoxelTerrainResources.java @@ -25,19 +25,24 @@ public class SharedVoxelTerrainResources { public static final int DRAW_INDIRECT_COMMAND_SIZE = 4 * Integer.BYTES; public static final int COUNTER_BUFFER_SIZE = Integer.BYTES; - public static final int TERRAIN_GENERATION_PUSH_CONSTANT_SIZE = Integer.BYTES * 8; + public static final int TERRAIN_GENERATION_PUSH_CONSTANT_SIZE = Integer.BYTES * 10; public static final int MAX_VOXEL_TYPES = 256; public static final int MAX_BIOME_TYPES = 256; public static final int VOXEL_REG_DATA_SIZE = 104; public static final int BIOME_REG_DATA_SIZE = 64; + public static final int MAX_STRUCTURE_TYPES = 128; + public static final int STRUCTURE_REG_DATA_SIZE = 36; + public static final int MAX_STRUCTURE_BLOCKS = 16 * 24 * 16; + private static final String DESC_ID_VOXEL_GENERATION = "VOXEL_SHARED_DESC_GENERATION"; private static final String DESC_ID_RESET = "VOXEL_SHARED_DESC_RESET"; private static final String DESC_ID_MESH = "VOXEL_SHARED_DESC_MESH"; private static final String DESC_ID_FACE_GRAPHICS = "VOXEL_SHARED_DESC_FACE_GRAPHICS"; private static final String DESC_ID_VOXEL_REG = "VOXEL_REGISTRY"; private static final String DESC_ID_BIOME_REG = "BIOME_REGISTRY"; + private static final String DESC_ID_STRUCTURE_REG = "STRUCTURE_REGISTRY"; private final VoxelMeshPool meshPool; @@ -45,6 +50,8 @@ public class SharedVoxelTerrainResources { private final VulkanBuffer counters; private final VulkanBuffer voxelRegistryBuffer; private final VulkanBuffer biomeRegistryBuffer; + private final VulkanBuffer structureRegistryBuffer; + private final VulkanBuffer structureVoxelDataBuffer; private final DescriptorSetLayout voxelGenerationLayout; private final DescriptorSetLayout resetLayout; @@ -52,6 +59,7 @@ public class SharedVoxelTerrainResources { private final DescriptorSetLayout faceGraphicsLayout; private final DescriptorSetLayout voxelDeclarationLayout; private final DescriptorSetLayout biomeDeclarationLayout; + private final DescriptorSetLayout structureDeclarationLayout; private static final String MESH_GENERATION_GLSL = "resources/EngineResources/VoxelComputeShaders/meshGenerationShared.glsl"; private static final String VOXEL_GENERATION_GLSL = "resources/EngineResources/VoxelComputeShaders/voxelGenerationShared.glsl"; @@ -82,6 +90,14 @@ public class SharedVoxelTerrainResources { VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + structureRegistryBuffer = new VulkanBuffer(VkCtx, (long) MAX_STRUCTURE_TYPES * STRUCTURE_REG_DATA_SIZE, + VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, + VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + structureVoxelDataBuffer = new VulkanBuffer(VkCtx, (long) MAX_STRUCTURE_TYPES * MAX_STRUCTURE_BLOCKS * Integer.BYTES, + VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, + VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); voxelGenerationLayout = new DescriptorSetLayout(VkCtx, new DescriptorSetLayout.LayoutInformation[]{ new DescriptorSetLayout.LayoutInformation(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 0, 1, VK_SHADER_STAGE_COMPUTE_BIT) @@ -109,6 +125,10 @@ public class SharedVoxelTerrainResources { biomeDeclarationLayout = new DescriptorSetLayout(VkCtx, new DescriptorSetLayout.LayoutInformation[]{ new DescriptorSetLayout.LayoutInformation(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 0, 1, VK_SHADER_STAGE_COMPUTE_BIT) }); + structureDeclarationLayout = new DescriptorSetLayout(VkCtx, new DescriptorSetLayout.LayoutInformation[]{ + new DescriptorSetLayout.LayoutInformation(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 0, 1, VK_SHADER_STAGE_COMPUTE_BIT), + new DescriptorSetLayout.LayoutInformation(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, 1, VK_SHADER_STAGE_COMPUTE_BIT) + }); createDescriptorSets(VkCtx); @@ -123,7 +143,7 @@ public class SharedVoxelTerrainResources { resetPipeline = new ComputePipeline(VkCtx,resetShader,new DescriptorSetLayout[]{resetLayout},generationPushConstants); voxelGenerationPipeline = new ComputePipeline(VkCtx,voxelShader, - new DescriptorSetLayout[]{voxelGenerationLayout,biomeDeclarationLayout},generationPushConstants); + new DescriptorSetLayout[]{voxelGenerationLayout,biomeDeclarationLayout, structureDeclarationLayout},generationPushConstants); meshGenerationPipeline = new ComputePipeline(VkCtx, meshShader, new DescriptorSetLayout[]{meshGenerationLayout, voxelDeclarationLayout}, generationPushConstants); @@ -166,6 +186,58 @@ public class SharedVoxelTerrainResources { DescriptorSet biomeDeclareDescriptorSet = allocator.AddDescriptorSet(device, DESC_ID_BIOME_REG, biomeDeclarationLayout); biomeDeclareDescriptorSet.SetBuffer(device, biomeRegistryBuffer, biomeRegistryBuffer.GetRequestedSize(), 0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER); + DescriptorSet structureDeclareDescriptorSet = allocator.AddDescriptorSet(device, DESC_ID_STRUCTURE_REG, structureDeclarationLayout); + structureDeclareDescriptorSet.SetBuffer(device, structureRegistryBuffer, structureRegistryBuffer.GetRequestedSize(), 0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER); + structureDeclareDescriptorSet.SetBuffer(device, structureVoxelDataBuffer, structureVoxelDataBuffer.GetRequestedSize(), 1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER); + + + } + + public void UploadStructureRegistryAndData(VulkanContext VkCtx, List structureReg, List structureData) { + int structureCount = Math.min(structureReg.size(), MAX_STRUCTURE_TYPES); + + long structureRegistryMemoryAddr = structureRegistryBuffer.MapMemory(VkCtx); + ByteBuffer structureRegistryByteBuffer = MemoryUtil.memByteBuffer( + structureRegistryMemoryAddr, + (int) structureRegistryBuffer.GetRequestedSize() + ); + + for (int i = 0; i < structureCount; i++) { + VoxelStructure structure = structureReg.get(i); + int base = i * STRUCTURE_REG_DATA_SIZE; + + int spawnChance = Math.round(structure.SpawnChance() * 1000.0f); + spawnChance = Math.clamp(spawnChance, 0, 1000); + + structureRegistryByteBuffer.putInt(base + 0, structure.SizeX()); + structureRegistryByteBuffer.putInt(base + 4, structure.SizeY()); + structureRegistryByteBuffer.putInt(base + 8, structure.SizeZ()); + structureRegistryByteBuffer.putInt(base + 12, structure.BlockOffset()); + structureRegistryByteBuffer.putInt(base + 16, structure.SpawnSpacing()); + structureRegistryByteBuffer.putInt(base + 20, spawnChance); + structureRegistryByteBuffer.putInt(base + 24, structure.AllowedBiome()); + structureRegistryByteBuffer.putInt(base + 28, structure.Flags()); + structureRegistryByteBuffer.putInt(base + 32, structure.GroundBlock()); + } + + structureRegistryBuffer.UnMapMemory(VkCtx); + + int blockCount = Math.min( + structureData.size(), + (int) (structureVoxelDataBuffer.GetRequestedSize() / Integer.BYTES) + ); + + long structureVoxelDataMemoryAddr = structureVoxelDataBuffer.MapMemory(VkCtx); + ByteBuffer structureVoxelDataByteBuffer = MemoryUtil.memByteBuffer( + structureVoxelDataMemoryAddr, + (int) structureVoxelDataBuffer.GetRequestedSize() + ); + + for (int i = 0; i < blockCount; i++) { + structureVoxelDataByteBuffer.putInt(i * Integer.BYTES, structureData.get(i)); + } + + structureVoxelDataBuffer.UnMapMemory(VkCtx); } public void UploadBiomeRegistry(VulkanContext VkCtx, List biomeReg) { @@ -269,10 +341,13 @@ public class SharedVoxelTerrainResources { return DESC_ID_BIOME_REG; } + public String structureRegDescriptorID() { + return DESC_ID_STRUCTURE_REG; + } + public String resetDescriptorId() { return DESC_ID_RESET; } - public String meshDescriptorId() { return DESC_ID_MESH; } diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Computing/VoxelTerrainGeneration/StructureFlags.java b/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Computing/VoxelTerrainGeneration/StructureFlags.java new file mode 100644 index 0000000..fd068ba --- /dev/null +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Computing/VoxelTerrainGeneration/StructureFlags.java @@ -0,0 +1,8 @@ +package net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Computing.VoxelTerrainGeneration; + +public final class StructureFlags { + public static final int REPLACE_AIR_ONLY = 1; + public static final int REPLACE_FOLIAGE = 2; + public static final int REQUIRE_FLAT_GROUND = 4; + public static final int HAS_FOUNDATION = 8; +} \ No newline at end of file diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Computing/VoxelTerrainGeneration/VoxelChunkGenerator.java b/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Computing/VoxelTerrainGeneration/VoxelChunkGenerator.java index e3698da..2fa1643 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Computing/VoxelTerrainGeneration/VoxelChunkGenerator.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Computing/VoxelTerrainGeneration/VoxelChunkGenerator.java @@ -41,12 +41,16 @@ public class VoxelChunkGenerator { pushConstants.putInt(20, VoxelWorldManager.GetVoxelTypeCount()); pushConstants.putInt(24, indirectCommandIndex); pushConstants.putInt(28, VoxelWorldManager.GetBiomeTypeCount()); + pushConstants.putInt(32, VoxelWorldManager.GetStructureTypeCount()); + pushConstants.putInt(36, VoxelWorldManager.GetWorldSeed()); LongBuffer resetDescriptorSet = stack.longs(allocator.GetDescriptorSet( resources.resetDescriptorId()).GetVkDescriptorSet()); - LongBuffer voxelDescriptorSet = stack.longs(allocator.GetDescriptorSet( - resources.voxelGenerationDescriptorId()).GetVkDescriptorSet(), - allocator.GetDescriptorSet(resources.biomeRegDescriptorID()).GetVkDescriptorSet()); + LongBuffer voxelDescriptorSet = stack.longs( + allocator.GetDescriptorSet(resources.voxelGenerationDescriptorId()).GetVkDescriptorSet(), + allocator.GetDescriptorSet(resources.biomeRegDescriptorID()).GetVkDescriptorSet(), + allocator.GetDescriptorSet(resources.structureRegDescriptorID()).GetVkDescriptorSet() + ); LongBuffer meshDescriptorSet = stack.longs( allocator.GetDescriptorSet(resources.meshDescriptorId()).GetVkDescriptorSet(), allocator.GetDescriptorSet(resources.voxelRegDescriptorID()).GetVkDescriptorSet()); diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Computing/VoxelTerrainGeneration/VoxelStructure.java b/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Computing/VoxelTerrainGeneration/VoxelStructure.java new file mode 100644 index 0000000..cdfbc19 --- /dev/null +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Computing/VoxelTerrainGeneration/VoxelStructure.java @@ -0,0 +1,4 @@ +package net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Computing.VoxelTerrainGeneration; + +public record VoxelStructure(int SizeX, int SizeY, int SizeZ, int BlockOffset, int SpawnSpacing, float SpawnChance, int AllowedBiome, int GroundBlock, int Flags) { +} diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Computing/VoxelTerrainGeneration/VoxelWorldManager.java b/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Computing/VoxelTerrainGeneration/VoxelWorldManager.java index 3510b02..9dc1f36 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Computing/VoxelTerrainGeneration/VoxelWorldManager.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Computing/VoxelTerrainGeneration/VoxelWorldManager.java @@ -9,11 +9,13 @@ import org.joml.*; import org.lwjgl.vulkan.VkCommandBuffer; import org.tinylog.Logger; +import java.lang.Math; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.CopyOnWriteArrayList; import static org.lwjgl.vulkan.VK13.*; @@ -27,14 +29,97 @@ public class VoxelWorldManager { private static final ConcurrentHashMap KnownChunks = new ConcurrentHashMap<>(); private static final ConcurrentHashMap CulledChunks = new ConcurrentHashMap<>(); - private static final List VoxelRegistry = new java.util.concurrent.CopyOnWriteArrayList<>(); + private static final List VoxelRegistry = new CopyOnWriteArrayList<>(); private static final ConcurrentHashMap VoxelNameToIndex = new ConcurrentHashMap<>(); private static volatile boolean RegistryDirty = false; - private static final List BiomeRegistry = new java.util.concurrent.CopyOnWriteArrayList<>(); + private static final List BiomeRegistry = new CopyOnWriteArrayList<>(); private static final ConcurrentHashMap BiomeNameToIndex = new ConcurrentHashMap<>(); private static volatile boolean BiomeRegistryDirty = false; + private static final List StructureRegistry = new CopyOnWriteArrayList<>(); + private static final List StructureBlockData = new CopyOnWriteArrayList<>(); + private static final ConcurrentHashMap StructureNameToIndex =new ConcurrentHashMap<>(); + private static volatile boolean StructureRegistryDirty = false; + + private static int worldSeed = 12432450; + + public static int GetWorldSeed(){ + return worldSeed; + } + public static int structureIndex(int x, int y, int z, int sizeY, int sizeZ) { + return x * sizeY * sizeZ + y * sizeZ + z; + } + + public static synchronized void AddStructure(String name, int sizeX, int sizeY, int sizeZ, int spawnSpacing, + float spawnChance, int allowedBiome, int groundBlock, int flags, + int[] denseBlocks) { + int blockOffset = StructureBlockData.size(); + + for (int block : denseBlocks) { + StructureBlockData.add(block); + } + + VoxelStructure structure = new VoxelStructure(sizeX, sizeY, sizeZ, blockOffset, + spawnSpacing, spawnChance, allowedBiome, groundBlock, flags); + + Integer existing = StructureNameToIndex.get(name); + + if (existing != null) { + StructureRegistry.set(existing, structure); + } else { + StructureNameToIndex.put(name, StructureRegistry.size()); + StructureRegistry.add(structure); + } + + StructureRegistryDirty = true; + } + public static List GetStructureRegistry() { + return new ArrayList<>(StructureRegistry); + } + + public static List GetStructureBlockData() { + return new ArrayList<>(StructureBlockData); + } + + public static VoxelStructure GetStructure(String name) { + Integer idx = StructureNameToIndex.get(name); + return idx == null ? null : StructureRegistry.get(idx); + } + + public static int GetStructureTypeID(String name) { + Integer idx = StructureNameToIndex.get(name); + return idx == null ? -1 : idx + 1; + } + + public static int GetStructureTypeCount() { + return StructureRegistry.size(); + } + + public static synchronized void GenerateExampleTree(int sizeX, int sizeY, int sizeZ, int Biome, int GroundVoxel, int log, int leaves, String name, int Spacing, float Chance){ + int[] blocks = new int[sizeX * sizeY * sizeZ]; + + for (int y = 0; y < sizeY/2; y++) { + blocks[structureIndex(sizeX/2, y, sizeZ/2, sizeY, sizeZ)] = log; + } + + for (int x = 0; x < sizeX; x++) { + for (int y = sizeY/2; y < sizeY; y++) { + for (int z = 0; z < sizeZ; z++) { + int dx = x - sizeX/2; + int dy = y - sizeY/5 * 3; + int dz = z - sizeZ/2; + + if (dx * dx + dy * dy + dz * dz <= sizeX) { + blocks[structureIndex(x, y, z, sizeY, sizeZ)] = leaves; + } + } + } + } + VoxelWorldManager.AddStructure(name, sizeX, sizeY, sizeZ, Spacing, Chance, + Biome, GroundVoxel, 0, blocks); + } + public static synchronized void AddVoxel(Voxel voxel, String name) { Integer existing = VoxelNameToIndex.get(name); if (existing != null) { @@ -198,6 +283,10 @@ public class VoxelWorldManager { BiomeRegistryDirty = false; Resources.UploadBiomeRegistry(VkCtx, GetBiomeRegistry()); } + if (StructureRegistryDirty) { + StructureRegistryDirty = false; + Resources.UploadStructureRegistryAndData(VkCtx, GetStructureRegistry(), GetStructureBlockData()); + } if (VoxelRegistry.isEmpty()) { return; // nothing to mesh against yet @@ -292,19 +381,19 @@ public class VoxelWorldManager { KnownChunks.remove(pos); return true; } -// int dx = pos.x - centerChunk.x; -// int dy = pos.y - centerChunk.y; -// int dz = pos.z - centerChunk.z; -// -// int distSq = dx * dx + dy * dy + dz * dz; -// if (distSq > maxDistSq) { -// RenderChunk chunk = entry.getValue(); -// -// Resources.meshPool().free(chunk); -// KnownChunks.remove(pos); -// -// return true; -// } + int dx = pos.x - centerChunk.x; + int dy = pos.y - centerChunk.y; + int dz = pos.z - centerChunk.z; + + int distSq = dx * dx + dy * dy + dz * dz; + if (distSq > maxDistSq) { + RenderChunk chunk = entry.getValue(); + + Resources.meshPool().free(chunk); + KnownChunks.remove(pos); + + return true; + } return false; });