-
Notifications
You must be signed in to change notification settings - Fork 17
Expand file tree
/
Copy pathLandscapeAudioCullingSystem.cs
More file actions
328 lines (279 loc) · 13.7 KB
/
Copy pathLandscapeAudioCullingSystem.cs
File metadata and controls
328 lines (279 loc) · 13.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
using Arch.Core;
using Arch.System;
using Arch.SystemGroups;
using Arch.SystemGroups.DefaultSystemGroups;
using DCL.CharacterCamera;
using DCL.Diagnostics;
using ECS.Abstract;
using System.Collections.Generic;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Profiling;
using Vector3 = UnityEngine.Vector3;
using DCL.Audio.Jobs;
using DCL.Landscape;
using DCL.Landscape.Systems;
using ECS;
using Random = UnityEngine.Random;
namespace DCL.Audio.Systems
{
/// <summary>
/// This system updates the audio state for every terrain chunk calculating the distance to its center and determining if its in "hearing distance"
/// This is done through a parallel job. If it should be heard, we enable the audioSources on the pre-calculated positions setting them to play the corresponding audio for their zone.
/// </summary>
[LogCategory(ReportCategory.AUDIO)]
[UpdateInGroup(typeof(PresentationSystemGroup))]
public partial class LandscapeAudioCullingSystem : BaseUnityLoopSystem
{
private readonly TerrainGenerator terrainGenerator;
private readonly WorldTerrainGenerator worldTerrainGenerator;
private readonly ILandscapeAudioSystemSettings landscapeAudioSystemSettings;
private readonly WorldAudioPlaybackController worldAudioPlaybackController;
private readonly IRealmData realmData;
private bool isTerrainViewInitialized;
private NativeArray<LandscapeAudioState> landscapeAudioStates;
private NativeArray<NativeArray<int2>> landscapeAudioSourcesPositions;
private NativeArray<LandscapeAudioState> oceanAudioStates;
private JobHandle landscapeJobHandle;
private JobHandle oceanJobHandle;
private float audioListeningDistanceThreshold;
private float audioMutingDistanceThreshold;
private float oceanListeningDistanceThreshold;
private ITerrain terrain;
private int updateFramesCounter;
private LandscapeAudioCullingSystem(World world,
TerrainGenerator terrainGenerator,
WorldTerrainGenerator worldTerrainGenerator,
ILandscapeAudioSystemSettings landscapeAudioSystemSettings,
WorldAudioPlaybackController worldAudioPlaybackController,
IRealmData realmData) : base(world)
{
this.terrainGenerator = terrainGenerator;
this.worldTerrainGenerator = worldTerrainGenerator;
this.landscapeAudioSystemSettings = landscapeAudioSystemSettings;
this.worldAudioPlaybackController = worldAudioPlaybackController;
this.realmData = realmData;
}
public override void Initialize()
{
base.Initialize();
oceanJobHandle = default(JobHandle);
landscapeJobHandle = default(JobHandle);
}
protected override void OnDispose()
{
landscapeJobHandle.Complete();
oceanJobHandle.Complete();
landscapeAudioStates.Dispose();
if (landscapeAudioSourcesPositions.IsCreated)
for (var i = 0; i < landscapeAudioSourcesPositions.Length; i++)
landscapeAudioSourcesPositions[i].Dispose();
landscapeAudioSourcesPositions.Dispose();
oceanAudioStates.Dispose();
}
protected override void Update(float t)
{
ITerrain currentTerrain = realmData.IsGenesis() ? terrainGenerator : worldTerrainGenerator;
if (currentTerrain != terrain)
{
if (isTerrainViewInitialized)
FlushPreviousAudioStates();
terrain = currentTerrain;
isTerrainViewInitialized = false;
updateFramesCounter = 0;
}
if (!terrain.IsTerrainShown) return;
if (updateFramesCounter <= 0)
{
if (!isTerrainViewInitialized)
{
InitializeTerrainAudioStates();
InitializeOceanAudioStates();
isTerrainViewInitialized = true;
}
if (isTerrainViewInitialized)
{
UpdateTerrainAudioEventsQuery(World);
UpdateOceanAudioEventsQuery(World);
}
updateFramesCounter = landscapeAudioSystemSettings.SystemUpdateFrequency;
}
else { updateFramesCounter--; }
}
private void FlushPreviousAudioStates()
{
for (int i = 0; i < landscapeAudioStates.Length; i++)
worldAudioPlaybackController.ReleaseAudioSourcesFromTerrain(i, WorldAudioClipType.Landscape);
for (int i = 0; i < oceanAudioStates.Length; i++)
worldAudioPlaybackController.ReleaseAudioSourcesFromTerrain(i, WorldAudioClipType.Ocean);
}
private void InitializeTerrainAudioStates()
{
int parcelSize = terrain.ParcelSize;
#pragma warning disable CS0612, CS0618 // reads the [Obsolete] World-Terrain ChunkModels array (live audio boundary)
ChunkModel[] terrainChunks = terrain.TerrainModel.ChunkModels;
#pragma warning restore CS0612, CS0618
landscapeAudioStates = new NativeArray<LandscapeAudioState>(terrainChunks.Length, Allocator.Persistent);
landscapeAudioSourcesPositions = new NativeArray<NativeArray<int2>>(terrainChunks.Length, Allocator.Persistent);
int chunkSize = terrain.GetChunkSize();
int halfTerrainChunkSize = chunkSize / 2;
audioListeningDistanceThreshold = math.pow(halfTerrainChunkSize + landscapeAudioSystemSettings.ListeningDistanceThreshold, 2);
audioMutingDistanceThreshold = math.pow(halfTerrainChunkSize + landscapeAudioSystemSettings.MutingDistanceThreshold, 2);
for (var i = 0; i < terrainChunks.Length; i++)
{
#pragma warning disable CS0612 // ChunkModel: live World-Terrain audio boundary
ChunkModel chunk = terrainChunks[i];
#pragma warning restore CS0612
int2 center = (chunk.MinParcel * parcelSize) + halfTerrainChunkSize;
landscapeAudioStates[i] = new LandscapeAudioState
{
CenterOfTerrain = new float2(center.x, center.y),
};
landscapeAudioSourcesPositions[i] = CalculateAudioSourcesPositions(chunk, chunkSize, parcelSize);
}
}
private void InitializeOceanAudioStates()
{
//To calculate the ocean audio, we will use the cliffs positions as the ocean grid does not match correctly with the world grid.
IReadOnlyList<Transform> cliffs = terrain.Cliffs;
oceanAudioStates = new NativeArray<LandscapeAudioState>(cliffs.Count, Allocator.Persistent);
for (var i = 0; i < cliffs.Count; i++)
{
Transform cliff = cliffs[i];
Vector3 position = cliff.transform.position;
oceanAudioStates[i] = new LandscapeAudioState
{
CenterOfTerrain = new float2(position.x, position.z),
};
}
oceanListeningDistanceThreshold = math.pow(landscapeAudioSystemSettings.OceanDistanceThreshold, 2);
}
#pragma warning disable CS0612 // ChunkModel param: live World-Terrain audio boundary
private NativeArray<int2> CalculateAudioSourcesPositions(ChunkModel terrain, int2 chunkSize, int parcelSize)
#pragma warning restore CS0612
{
int rowsPerChunk = landscapeAudioSystemSettings.RowsPerChunk;
int retryAttempts = landscapeAudioSystemSettings.AudioSourcePositioningRetryAttempts;
int cellWidth = chunkSize.x / rowsPerChunk;
int cellLength = chunkSize.y / rowsPerChunk;
var positions = new NativeList<int2>(rowsPerChunk * rowsPerChunk, Allocator.Persistent);
for (var row = 0; row < rowsPerChunk; row++)
{
for (var col = 0; col < rowsPerChunk; col++)
{
var localCellCenter = new int2(
(col * cellWidth) + (cellWidth / 2),
(row * cellLength) + (cellLength / 2)
);
for (var retry = 0; retry < retryAttempts; retry++)
{
var randomOffset = new int2(Random.Range(-cellWidth / 2, cellWidth / 2), Random.Range(-cellLength / 2, cellLength / 2));
int2 randomPosition = localCellCenter + randomOffset;
if (!terrain.IsOutsideOrOccupied(terrain.MinParcel + (randomPosition / parcelSize)))
{
positions.Add((terrain.MinParcel * parcelSize) + randomPosition);
break;
}
}
}
}
return positions.AsArray();
}
[Query]
private void UpdateTerrainAudioEvents(in Entity _, in CameraComponent cameraComponent)
{
if (landscapeJobHandle.IsCompleted && !landscapeJobHandle.Equals(default(JobHandle)))
{
Profiler.BeginSample("UpdateTerrainAudioEvents.Update");
landscapeJobHandle.Complete();
for (var i = 0; i < landscapeAudioStates.Length; i++)
{
LandscapeAudioState landscapeAudioState = landscapeAudioStates[i];
TerrainAudioState audioState = landscapeAudioState.AudioState;
if (audioState is { ShouldBeHeard: true, IsHeard: false })
{
audioState.IsHeard = true;
landscapeAudioState.AudioState = audioState;
landscapeAudioStates[i] = landscapeAudioState;
worldAudioPlaybackController.SetupAudioSourcesOnTerrain(i, landscapeAudioSourcesPositions[i], WorldAudioClipType.Landscape);
}
else if (audioState is { ShouldBeSilent: true, IsSilent: false })
{
audioState.IsSilent = true;
landscapeAudioState.AudioState = audioState;
landscapeAudioStates[i] = landscapeAudioState;
worldAudioPlaybackController.ReleaseAudioSourcesFromTerrain(i, WorldAudioClipType.Landscape);
}
}
Profiler.EndSample();
}
// Schedule
if (landscapeJobHandle.IsCompleted)
{
Profiler.BeginSample("CalculateLandscapeAudioStatesJob.Schedule");
landscapeJobHandle.Complete();
Vector3 position = cameraComponent.Camera.transform.position;
var cameraPosition = new float2(position.x, position.z);
var job = new CalculateLandscapeAudioStatesJob(landscapeAudioStates, cameraPosition, audioListeningDistanceThreshold, audioMutingDistanceThreshold);
landscapeJobHandle = job.Schedule(landscapeAudioStates.Length, 32, landscapeJobHandle);
Profiler.EndSample();
}
}
[Query]
private void UpdateOceanAudioEvents(in Entity _, in CameraComponent cameraComponent)
{
if (oceanJobHandle.IsCompleted && !oceanJobHandle.Equals(default(JobHandle)))
{
Profiler.BeginSample("UpdateOceanAudioEvents.Update");
oceanJobHandle.Complete();
for (var i = 0; i < oceanAudioStates.Length; i++)
{
LandscapeAudioState oceanAudioState = oceanAudioStates[i];
TerrainAudioState audioState = oceanAudioState.AudioState;
if (audioState is { ShouldBeHeard: true, IsHeard: false })
{
audioState.IsHeard = true;
oceanAudioState.AudioState = audioState;
oceanAudioStates[i] = oceanAudioState;
var closestPointArray = new NativeArray<int2>(1, Allocator.Temp);
closestPointArray[0] = (int2)oceanAudioState.CenterOfTerrain;
worldAudioPlaybackController.SetupAudioSourcesOnTerrain(i, closestPointArray, WorldAudioClipType.Ocean);
}
else if (audioState is { ShouldBeSilent: true, IsSilent: false })
{
audioState.IsSilent = true;
oceanAudioState.AudioState = audioState;
oceanAudioStates[i] = oceanAudioState;
worldAudioPlaybackController.ReleaseAudioSourcesFromTerrain(i, WorldAudioClipType.Ocean);
}
}
Profiler.EndSample();
}
// Schedule
if (oceanJobHandle.IsCompleted)
{
Profiler.BeginSample("CalculateOceanAudioStatesJob.Schedule");
oceanJobHandle.Complete();
Vector3 position = cameraComponent.Camera.transform.position;
var cameraPosition = new float2(position.x, position.z);
var job = new CalculateOceanAudioStatesJob(oceanAudioStates, cameraPosition, oceanListeningDistanceThreshold);
oceanJobHandle = job.Schedule(oceanAudioStates.Length, 32, oceanJobHandle);
Profiler.EndSample();
}
}
}
public struct TerrainAudioState
{
public bool IsSilent;
public bool IsHeard;
public bool ShouldBeSilent;
public bool ShouldBeHeard;
}
public struct LandscapeAudioState
{
public TerrainAudioState AudioState;
public float2 CenterOfTerrain;
}
}