Hello
i am creating a 3D Voxel based game(voxels are arranged in 3D array), where the game area is divide in chunks. I am trying to implement somewhat realistic water physics based on voxels of type water that can hold water within.
Water should be able to:
- spread across horizontally, example, if I place 1 full water voxel in a box 2x2 then after physics calculations should be done there should be ~ 0.25 water in each.
- flow vertically down like waterfalls
- (optional) ideally I would want in some interaction to make the fluid have density and pressure, and momentum
i have tried for a few days myself in multiple iterations with something like this
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using UnityEngine;
[BurstCompile]
public struct WaterFlowJob : IJob {
public NativeArray<Voxel> voxels;
public int chunkSize;
public float deltaTime;
private const int IterationsPerExecute = 3;
private const float MaxFlowRate = 0.1f;
private const float MinWaterThreshold = 0.01f;
public void Execute() {
NativeArray<bool> isInitialWaterBlock = new NativeArray<bool>(voxels.Length, Allocator.Temp);
NativeArray<bool> updatedVoxels = new NativeArray<bool>(voxels.Length, Allocator.Temp);
for (int i = 0; i < voxels.Length; i++) {
if (voxels[i].type == VoxelType.Water && voxels[i].waterLevel >= MinWaterThreshold) {
isInitialWaterBlock[i] = true;
} else {
isInitialWaterBlock[i] = false;
}
}
for (int iteration = 0; iteration < IterationsPerExecute; iteration++) {
for (int y = 0; y < chunkSize; y++) {
for (int x = 0; x < chunkSize; x++) {
for (int z = 0; z < chunkSize; z++) {
int index = GetIndex(x, y, z, chunkSize);
if (!isInitialWaterBlock[index]) {
continue;
}
Voxel currentVoxel = voxels[index];
if (voxels[index].type == VoxelType.Water && currentVoxel.waterLevel < MinWaterThreshold) {
currentVoxel.isActive = false;
currentVoxel.type = VoxelType.Air;
voxels[index] = currentVoxel;
}
Vector3Int[] directions = { Vector3Int.left, Vector3Int.right, Vector3Int.forward, Vector3Int.back };
foreach (var dir in directions) {
int neighborX = x + dir.x;
int neighborZ = z + dir.z;
if (IsInBounds(neighborX, y, neighborZ, chunkSize)) {
int neighborIndex = GetIndex(neighborX, y, neighborZ, chunkSize);
Voxel neighborVoxel = voxels[neighborIndex];
if (neighborVoxel.type == VoxelType.Water &&
neighborVoxel.waterLevel < currentVoxel.waterLevel &&
(currentVoxel.waterLevel - neighborVoxel.waterLevel) > MinWaterThreshold) {
float flowAmount = Mathf.Max((currentVoxel.waterLevel - neighborVoxel.waterLevel) * MaxFlowRate, MinWaterThreshold);
if (flowAmount > 0) {
Voxel tempNeighborVoxel = voxels[neighborIndex];
tempNeighborVoxel.waterLevel += flowAmount;
currentVoxel.waterLevel -= flowAmount;
voxels[neighborIndex] = tempNeighborVoxel;
voxels[index] = currentVoxel;
}
}
if (!updatedVoxels[neighborIndex]) {
if (neighborVoxel.type == VoxelType.Air) {
float flowAmount = Mathf.Max(currentVoxel.waterLevel * MaxFlowRate, MinWaterThreshold);
if (flowAmount > 0) {
Voxel tempNeighborVoxel = voxels[neighborIndex];
tempNeighborVoxel.isActive = true;
tempNeighborVoxel.type = VoxelType.Water;
tempNeighborVoxel.waterLevel += flowAmount;
currentVoxel.waterLevel -= flowAmount;
voxels[neighborIndex] = tempNeighborVoxel;
voxels[index] = currentVoxel;
updatedVoxels[neighborIndex] = true;
}
}
}
}
}
}
}
}
}
isInitialWaterBlock.Dispose();
updatedVoxels.Dispose();
}
private int GetIndex(int x, int y, int z, int size) {
return x * size * size + y * size + z;
}
private bool IsInBounds(int x, int y, int z, int size) {
return x >= 0 && x < size && y >= 0 && y < size && z >= 0 && z < size;
}
}
But i am not sure if I am going in the right direction. I us IJob so that water calculations are done in the background. The world would not be infinite but I want to create it so that it can also run on mobile devices.
Any suggestions on how to better do this. I am new to developing voxel based games, and to game development in general but have been a programmer a while. Maybe there are approaches i cant think about.