using UnityEngine;
using System.Collections.Generic;
using Unity.Collections;
using Unity.Jobs;
using Unity.Burst;
using Unity.Mathematics;
using WaterSystem.Data;
namespace WaterSystem
{
///
/// C# Jobs system version of the Gerstner waves implimentation
///
public static class GerstnerWavesJobs
{
//General variables
public static bool init;
public static bool firstFrame = true;
public static bool processing = false;
static int _waveCount;
static NativeArray waveData; // Wave data from the water system
//Details for Buoyant Objects
static NativeArray positions;
static int positionCount = 0;
static NativeArray wavePos;
static NativeArray tempNullNormal;
static JobHandle waterHeightHandle;
static Dictionary registry = new Dictionary();
//Details for Simple Buoyant Objects
static NativeArray simplePositions;
static int simplePositionCount = 0;
static NativeArray waveSimplePos;
static NativeArray waveSimpleNormal;
static JobHandle waterSimpleHeightHandle;
static Dictionary simpleRegistry = new Dictionary();
public static void Init()
{
//Wave data
_waveCount = Water.Instance._waves.Length;
waveData = new NativeArray(_waveCount, Allocator.Persistent);
for (var i = 0; i < waveData.Length; i++)
{
waveData[i] = Water.Instance._waves[i];
}
positions = new NativeArray(4096, Allocator.Persistent);
wavePos = new NativeArray(4096, Allocator.Persistent);
simplePositions = new NativeArray(1024, Allocator.Persistent);
waveSimplePos = new NativeArray(1024, Allocator.Persistent);
waveSimpleNormal = new NativeArray(1024, Allocator.Persistent);
tempNullNormal = new NativeArray(1, Allocator.Persistent);
init = true;
}
public static void Cleanup()
{
Debug.LogWarning("Cleaning up GerstnerWaves");
waterHeightHandle.Complete();
waterSimpleHeightHandle.Complete();
//Cleanup native arrays
waveData.Dispose();
tempNullNormal.Dispose();
positions.Dispose();
wavePos.Dispose();
simplePositions.Dispose();
waveSimplePos.Dispose();
waveSimpleNormal.Dispose();
}
public static void UpdateSamplePoints(float3[] samplePoints, int guid, bool simple)
{
CompleteJobs();
int2 offsets;
if (simple)
{
if (simpleRegistry.TryGetValue(guid, out offsets))
{
for (var i = offsets.x; i < offsets.y; i++) simplePositions[i] = samplePoints[i - offsets.x];
}
else
{
if (simplePositionCount + samplePoints.Length < simplePositions.Length)
{
offsets = new int2(simplePositionCount, simplePositionCount + samplePoints.Length);
//Debug.Log("Adding Object:" + guid + " to the simple registry at offset:" + offsets + "");
simpleRegistry.Add(guid, offsets);
simplePositionCount += samplePoints.Length;
}
}
}
else
{
if (registry.TryGetValue(guid, out offsets))
{
for (var i = offsets.x; i < offsets.y; i++) positions[i] = samplePoints[i - offsets.x];
}
else
{
if (positionCount + samplePoints.Length < positions.Length)
{
offsets = new int2(positionCount, positionCount + samplePoints.Length);
//Debug.Log("Adding Object:" + guid + " to the registry at offset:" + offsets + "");
registry.Add(guid, offsets);
positionCount += samplePoints.Length;
}
}
}
}
public static void GetSimpleData(int guid, ref float3[] outPos, ref float3[] outNorm)
{
var offsets = new int2(0, 0);
if (simpleRegistry.TryGetValue(guid, out offsets))
{
waveSimplePos.Slice(offsets.x, offsets.y - offsets.x).CopyTo(outPos);
waveSimpleNormal.Slice(offsets.x, offsets.y - offsets.x).CopyTo(outNorm);
}
}
public static void GetData(int guid, ref float3[] outPos)
{
var offsets = new int2(0, 0);
if (registry.TryGetValue(guid, out offsets))
{
wavePos.Slice(offsets.x, offsets.y - offsets.x).CopyTo(outPos);
}
}
// Height jobs for the next frame
public static void UpdateHeights()
{
if (!processing)
{
processing = true;
// Buoyant Object Job
var waterHeight = new GerstnerWavesJobs.HeightJob()
{
waveData = waveData,
position = positions,
offsetLength = new int2(0, positions.Length),
time = Time.time,
outPosition = wavePos,
outNormal = tempNullNormal,
normal = 0
};
// dependant on job4
waterHeightHandle = waterHeight.Schedule(positionCount, 32);
// Simple Buoyant Object Job
var waterSimpleHeight = new GerstnerWavesJobs.HeightJob()
{
waveData = waveData,
position = simplePositions,
offsetLength = new int2(0, simplePositions.Length),
time = Time.time,
outPosition = waveSimplePos,
outNormal = waveSimpleNormal,
normal = 1
};
// dependant on job4
waterSimpleHeightHandle = waterSimpleHeight.Schedule(simplePositionCount, 32);
JobHandle.ScheduleBatchedJobs();
firstFrame = false;
}
}
public static void CompleteJobs()
{
if (!firstFrame && processing)
{
waterHeightHandle.Complete();
waterSimpleHeightHandle.Complete();
processing = false;
}
}
// Gerstner Height C# Job
[BurstCompile]
public struct HeightJob : IJobParallelFor
{
[ReadOnly]
public NativeArray waveData; // wave data stroed in vec4's like the shader version but packed into one
[ReadOnly]
public NativeArray position;
[WriteOnly]
public NativeArray outPosition;
[WriteOnly]
public NativeArray outNormal;
[ReadOnly]
public float time;
[ReadOnly]
public int2 offsetLength;
[ReadOnly]
public int normal;
// The code actually running on the job
public void Execute(int i)
{
if (i >= offsetLength.x && i < offsetLength.y - offsetLength.x)
{
var waveCountMulti = 1f / waveData.Length;
float3 wavePos = new float3(0f, 0f, 0f);
float3 waveNorm = new float3(0f, 0f, 0f);
for (var wave = 0; wave < waveData.Length; wave++) // for each wave
{
// Wave data vars
var pos = position[i].xz;
var amplitude = waveData[wave].amplitude;
var direction = waveData[wave].direction;
var wavelength = waveData[wave].wavelength;
var omniPos = waveData[wave].origin;
////////////////////////////////wave value calculations//////////////////////////
var w = 6.28318f / wavelength; // 2pi over wavelength(hardcoded)
var wSpeed = math.sqrt(9.8f * w); // frequency of the wave based off wavelength
var peak = 0.8f; // peak value, 1 is the sharpest peaks
var qi = peak / (amplitude * w * waveData.Length);
var windDir = new float2(0f, 0f);
var dir = 0f;
direction = math.radians(direction); // convert the incoming degrees to radians
var windDirInput = new float2(math.sin(direction), math.cos(direction)) * (1 - waveData[wave].onmiDir); // calculate wind direction - TODO - currently radians
var windOmniInput = (pos - omniPos) * waveData[wave].onmiDir;
windDir += windDirInput;
windDir += windOmniInput;
windDir = math.normalize(windDir);
dir = math.dot(windDir, pos - (omniPos * waveData[wave].onmiDir)); // calculate a gradient along the wind direction
////////////////////////////position output calculations/////////////////////////
var calc = dir * w + -time * wSpeed; // the wave calculation
var cosCalc = math.cos(calc); // cosine version(used for horizontal undulation)
var sinCalc = math.sin(calc); // sin version(used for vertical undulation)
// calculate the offsets for the current point
wavePos.x += qi * amplitude * windDir.x * cosCalc;
wavePos.z += qi * amplitude * windDir.y * cosCalc;
wavePos.y += ((sinCalc * amplitude)) * waveCountMulti; // the height is divided by the number of waves
if (normal == 1)
{
////////////////////////////normal output calculations/////////////////////////
float wa = w * amplitude;
// normal vector
float3 norm = new float3(-(windDir.xy * wa * cosCalc),
1 - (qi * wa * sinCalc));
waveNorm += (norm * waveCountMulti) * amplitude;
}
}
outPosition[i] = wavePos;
if (normal == 1) outNormal[i] = math.normalize(waveNorm.xzy);
}
}
}
}
}