-
Notifications
You must be signed in to change notification settings - Fork 2.5k
/
Copy pathPaintContext.cs
679 lines (591 loc) · 34.2 KB
/
PaintContext.cs
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
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
// Unity C# reference source
// Copyright (c) Unity Technologies. For terms of use, see
// https://unity3d.com/legal/licenses/Unity_Reference_Only_License
using System;
using System.Collections.Generic;
using uei = UnityEngine.Internal;
using UnityEngine.Rendering;
using UnityEngine.Experimental.Rendering;
using UnityEngine.Profiling;
using UnityEngine.Scripting.APIUpdating;
namespace UnityEngine.TerrainTools
{
[MovedFrom("UnityEngine.Experimental.TerrainAPI")]
public class PaintContext
{
// initialized by constructor
public Terrain originTerrain { get; } // the terrain that defines the coordinate system and world space position of this PaintContext
public RectInt pixelRect { get; } // the rectangle, in target texture pixels on the originTerrain, that this paint context represents
public int targetTextureWidth { get; } // the size of the target texture, per terrain tile
public int targetTextureHeight { get; } // the size of the target texture, per terrain tile
public Vector2 pixelSize { get; } // size of a paint context pixel in object/terrain/world space
// initialized by CreateRenderTargets()
public RenderTexture sourceRenderTexture { get; private set; } // the original data
public RenderTexture destinationRenderTexture { get; private set; } // the modified data (you render to this)
public RenderTexture oldRenderTexture { get; private set; } // active render texture at the time CreateRenderTargets() is called, restored on Cleanup()
public int terrainCount { get { return m_TerrainTiles.Count; } }
public Terrain GetTerrain(int terrainIndex)
{
return m_TerrainTiles[terrainIndex].terrain;
}
public RectInt GetClippedPixelRectInTerrainPixels(int terrainIndex)
{
return m_TerrainTiles[terrainIndex].clippedTerrainPixels;
}
public RectInt GetClippedPixelRectInRenderTexturePixels(int terrainIndex)
{
return m_TerrainTiles[terrainIndex].clippedPCPixels;
}
// initialized by constructor
private List<TerrainTile> m_TerrainTiles; // all terrain tiles touched by this paint context
private float m_HeightWorldSpaceMin;
private float m_HeightWorldSpaceMax;
public float heightWorldSpaceMin => m_HeightWorldSpaceMin;
public float heightWorldSpaceSize => m_HeightWorldSpaceMax - m_HeightWorldSpaceMin;
public interface ITerrainInfo
{
Terrain terrain { get; } // the terrain tile
RectInt clippedTerrainPixels { get; } // the region modified by the PaintContext, in target texture pixels
RectInt clippedPCPixels { get; } // the region modified by the PaintContext, in PaintContext.sourceRenderTexture or destinationRenderTexture pixels
RectInt paddedTerrainPixels { get; } // a padded version of clippedTerrainPixels, used for extended-edge sampling to fill empty space
RectInt paddedPCPixels { get; } // a padded version of clippedPCPixels, used for extended-edge sampling to fill empty space
bool gatherEnable { get; set; } // user tools can disable gathering of this terrain tile by setting this flag (default true)
bool scatterEnable { get; set; } // user tools can disable scattering to this terrain tile by setting this flag (default true)
object userData { get; set; } // user tools can use this to associate data with the terrain
}
private class TerrainTile : ITerrainInfo
{
public Terrain terrain; // the terrain object for this tile
public Vector2Int tileOriginPixels; // coordinates of this terrain tile in originTerrain target texture pixels
public RectInt clippedTerrainPixels; // the tile pixels touched by this PaintContext (in terrain-local target texture pixels)
public RectInt clippedPCPixels; // the tile pixels touched by this PaintContext (in PaintContext/source/destRenderTexture pixels)
public RectInt paddedTerrainPixels; // a padded version of clippedTerrainPixels, used for extended-edge sampling
public RectInt paddedPCPixels; // a padded version of clippedPCPixels, used for extended-edge sampling
public object userData; // user data stash
public bool gatherEnable; // user controls for read/write
public bool scatterEnable;
Terrain ITerrainInfo.terrain { get { return terrain; } }
RectInt ITerrainInfo.clippedTerrainPixels { get { return clippedTerrainPixels; } }
RectInt ITerrainInfo.clippedPCPixels { get { return clippedPCPixels; } }
RectInt ITerrainInfo.paddedTerrainPixels { get { return paddedTerrainPixels; } }
RectInt ITerrainInfo.paddedPCPixels { get { return paddedPCPixels; } }
bool ITerrainInfo.gatherEnable { get { return gatherEnable; } set { gatherEnable = value; } }
bool ITerrainInfo.scatterEnable { get { return scatterEnable; } set { scatterEnable = value; } }
object ITerrainInfo.userData { get { return userData; } set { userData = value; } }
public static TerrainTile Make(Terrain terrain, int tileOriginPixelsX, int tileOriginPixelsY,
RectInt pixelRect, int targetTextureWidth, int targetTextureHeight, int edgePad = 0)
{
var tile = new TerrainTile()
{
terrain = terrain,
gatherEnable = true,
scatterEnable = true,
tileOriginPixels = new Vector2Int(tileOriginPixelsX, tileOriginPixelsY),
clippedTerrainPixels = new RectInt()
{
x = Mathf.Max(0, pixelRect.x - tileOriginPixelsX),
y = Mathf.Max(0, pixelRect.y - tileOriginPixelsY),
xMax = Mathf.Min(targetTextureWidth, pixelRect.xMax - tileOriginPixelsX),
yMax = Mathf.Min(targetTextureHeight, pixelRect.yMax - tileOriginPixelsY)
},
};
tile.clippedPCPixels = new RectInt(
tile.clippedTerrainPixels.x + tile.tileOriginPixels.x - pixelRect.x,
tile.clippedTerrainPixels.y + tile.tileOriginPixels.y - pixelRect.y,
tile.clippedTerrainPixels.width,
tile.clippedTerrainPixels.height);
// Optimize padding by removing it on edges that have a neighbor.
int leftPad = terrain.leftNeighbor == null ? edgePad : 0;
int rightPad = terrain.rightNeighbor == null ? edgePad : 0;
int bottomPad = terrain.bottomNeighbor == null ? edgePad : 0;
int topPad = terrain.topNeighbor == null ? edgePad : 0;
// Redo same clipping as clippedTerrainPixels, but on padded version of terrain.
tile.paddedTerrainPixels = new RectInt()
{
x = Mathf.Max(-leftPad, pixelRect.x - tileOriginPixelsX - leftPad),
y = Mathf.Max(-bottomPad, pixelRect.y - tileOriginPixelsY - bottomPad),
xMax = Mathf.Min(targetTextureWidth + rightPad, pixelRect.xMax - tileOriginPixelsX + rightPad),
yMax = Mathf.Min(targetTextureHeight + topPad, pixelRect.yMax - tileOriginPixelsY + topPad)
};
// PaddedPCPixels is equal to clippedPCPixels padded by the same amount as terrainPixels.
tile.paddedPCPixels = new RectInt(
tile.clippedPCPixels.min + (tile.paddedTerrainPixels.min - tile.clippedTerrainPixels.min),
tile.clippedPCPixels.size + (tile.paddedTerrainPixels.size - tile.clippedTerrainPixels.size));
if (tile.clippedTerrainPixels.width == 0 || tile.clippedTerrainPixels.height == 0)
{
tile.gatherEnable = false;
tile.scatterEnable = false;
Debug.LogError("PaintContext.ClipTerrainTiles found 0 content rect"); // we really shouldn't ever have this..
}
return tile;
}
}
private class SplatmapUserData // splatmap operation data per Terrain tile
{
public TerrainLayer terrainLayer; // the terrain layer we are concerned with
public int terrainLayerIndex; // the terrain layer index on this Terrain
public int mapIndex; // the splatmap index on this Terrain containing the desired TerrainLayer weight
public int channelIndex; // the channel on the splatmap containing the desired TerrainLayer weight
}
[Flags]
internal enum ToolAction
{
None = 0,
PaintHeightmap = 1 << 0,
PaintTexture = 1 << 1,
PaintHoles = 1 << 2,
AddTerrainLayer = 1 << 3
}
public static float kNormalizedHeightScale => 32766.0f / 65535.0f;
// TerrainPaintUtilityEditor hooks to this event to do automatic undo
internal static event Action<PaintContext.ITerrainInfo, ToolAction, string /*editorUndoName*/> onTerrainTileBeforePaint;
internal const int k_MinimumResolution = 1;
internal const int k_MaximumResolution = 8192;
internal static int ClampContextResolution(int resolution)
{
return Mathf.Clamp(resolution, k_MinimumResolution, k_MaximumResolution);
}
public PaintContext(
Terrain terrain, RectInt pixelRect, int targetTextureWidth, int targetTextureHeight,
[uei.DefaultValue("true")] bool sharedBoundaryTexel = true,
[uei.DefaultValue("true")] bool fillOutsideTerrain = true)
{
this.originTerrain = terrain;
this.pixelRect = pixelRect;
this.targetTextureWidth = targetTextureWidth;
this.targetTextureHeight = targetTextureHeight;
TerrainData terrainData = terrain.terrainData;
this.pixelSize = new Vector2(
terrainData.size.x / (targetTextureWidth - (sharedBoundaryTexel ? 1.0f : 0.0f)),
terrainData.size.z / (targetTextureHeight - (sharedBoundaryTexel ? 1.0f : 0.0f)));
FindTerrainTilesUnlimited(sharedBoundaryTexel, fillOutsideTerrain);
}
public static PaintContext CreateFromBounds(
Terrain terrain, Rect boundsInTerrainSpace, int inputTextureWidth, int inputTextureHeight,
[uei.DefaultValue("0")] int extraBorderPixels = 0,
[uei.DefaultValue("true")] bool sharedBoundaryTexel = true,
[uei.DefaultValue("true")] bool fillOutsideTerrain = true)
{
return new PaintContext(
terrain,
TerrainPaintUtility.CalcPixelRectFromBounds(terrain, boundsInTerrainSpace, inputTextureWidth,
inputTextureHeight, extraBorderPixels, sharedBoundaryTexel),
inputTextureWidth, inputTextureHeight, sharedBoundaryTexel, fillOutsideTerrain);
}
private void FindTerrainTilesUnlimited(bool sharedBoundaryTexel, bool fillOutsideTerrain)
{
// pixel rect bounds (in world space)
float minX = originTerrain.transform.position.x + pixelSize.x * pixelRect.xMin;
float minZ = originTerrain.transform.position.z + pixelSize.y * pixelRect.yMin;
float maxX = originTerrain.transform.position.x + pixelSize.x * (pixelRect.xMax - 1);
float maxZ = originTerrain.transform.position.z + pixelSize.y * (pixelRect.yMax - 1);
m_HeightWorldSpaceMin = originTerrain.GetPosition().y;
m_HeightWorldSpaceMax = m_HeightWorldSpaceMin + originTerrain.terrainData.size.y;
// this filter limits the search to Terrains that overlap the pixel rect bounds
Predicate<Terrain> filterOverlap =
t =>
{
// terrain bounds (in world space)
float tminX = t.transform.position.x;
float tminZ = t.transform.position.z;
float tmaxX = t.transform.position.x + t.terrainData.size.x;
float tmaxZ = t.transform.position.z + t.terrainData.size.z;
// test overlap
return (tminX <= maxX) && (tmaxX >= minX)
&& (tminZ <= maxZ) && (tmaxZ >= minZ);
};
// gather Terrains that pass the filter
TerrainUtils.TerrainMap terrainMap = TerrainUtils.TerrainMap.CreateFromConnectedNeighbors(originTerrain, filterOverlap, false);
// convert those Terrains into the TerrainTile list
m_TerrainTiles = new List<TerrainTile>();
if (terrainMap != null)
{
foreach (var cur in terrainMap.terrainTiles)
{
var coord = cur.Key;
Terrain terrain = cur.Value;
int minPixelX = coord.tileX * (targetTextureWidth - (sharedBoundaryTexel ? 1 : 0));
int minPixelZ = coord.tileZ * (targetTextureHeight - (sharedBoundaryTexel ? 1 : 0));
RectInt terrainPixelRect = new RectInt(minPixelX, minPixelZ, targetTextureWidth, targetTextureHeight);
if (pixelRect.Overlaps(terrainPixelRect))
{
// EdgePad fills empty regions outside terrains in PaintContext.
int edgePad = fillOutsideTerrain ? Mathf.Max(targetTextureWidth, targetTextureHeight) : 0;
m_TerrainTiles.Add(
TerrainTile.Make(
terrain,
minPixelX,
minPixelZ,
pixelRect,
targetTextureWidth,
targetTextureHeight,
edgePad));
m_HeightWorldSpaceMin = Mathf.Min(m_HeightWorldSpaceMin, terrain.GetPosition().y);
m_HeightWorldSpaceMax = Mathf.Max(m_HeightWorldSpaceMax, terrain.GetPosition().y + terrain.terrainData.size.y);
}
}
}
}
public void CreateRenderTargets(RenderTextureFormat colorFormat)
{
// Extended edge sampling of tiles requires a depth buffer (see TerrainPaintUtility.DrawQuadPadded for more info).
int width = ClampContextResolution(pixelRect.width);
int height = ClampContextResolution(pixelRect.height);
if (width != pixelRect.width || height != pixelRect.height)
{
Debug.LogWarning($@"
TERRAIN EDITOR INTERNAL ERROR: An attempt to create a PaintContext with dimensions of {pixelRect.width}x{pixelRect.height} was made,
whereas the maximum supported resolution is {k_MaximumResolution}. The size has been clamped to {k_MaximumResolution}."
);
}
sourceRenderTexture = RenderTexture.GetTemporary(width, height, 16, colorFormat, RenderTextureReadWrite.Linear);
destinationRenderTexture = RenderTexture.GetTemporary(width, height, 0, colorFormat, RenderTextureReadWrite.Linear);
sourceRenderTexture.wrapMode = TextureWrapMode.Clamp;
sourceRenderTexture.filterMode = FilterMode.Point;
oldRenderTexture = RenderTexture.active;
}
public void Cleanup(bool restoreRenderTexture = true)
{
if (restoreRenderTexture)
RenderTexture.active = oldRenderTexture;
RenderTexture.ReleaseTemporary(sourceRenderTexture);
RenderTexture.ReleaseTemporary(destinationRenderTexture);
sourceRenderTexture = null;
destinationRenderTexture = null;
oldRenderTexture = null;
}
private void GatherInternal(
Func<ITerrainInfo, Texture> terrainToTexture,
Color defaultColor,
string operationName,
Material blitMaterial = null,
int blitPass = 0,
Action<ITerrainInfo> beforeBlit = null,
Action<ITerrainInfo> afterBlit = null)
{
if (blitMaterial == null)
blitMaterial = TerrainPaintUtility.GetBlitMaterial();
RenderTexture.active = sourceRenderTexture;
GL.Clear(true, true, defaultColor);
GL.PushMatrix();
GL.LoadPixelMatrix(0, pixelRect.width, 0, pixelRect.height);
for (int i = 0; i < m_TerrainTiles.Count; i++)
{
TerrainTile terrainTile = m_TerrainTiles[i];
if (!terrainTile.gatherEnable)
continue;
Texture sourceTexture = terrainToTexture(terrainTile);
if ((sourceTexture == null) || (!terrainTile.gatherEnable)) // double check gatherEnable in case terrainToTexture modified it
continue;
if ((sourceTexture.width != targetTextureWidth) || (sourceTexture.height != targetTextureHeight))
{
Debug.LogWarning(operationName + " requires the same resolution texture for all Terrains - mismatched Terrains are ignored.", terrainTile.terrain);
continue;
}
beforeBlit?.Invoke(terrainTile);
if (!terrainTile.gatherEnable) // check again, beforeBlit may have modified it
continue;
FilterMode oldFilterMode = sourceTexture.filterMode;
sourceTexture.filterMode = FilterMode.Point;
blitMaterial.SetTexture("_MainTex", sourceTexture);
blitMaterial.SetPass(blitPass);
// Draw padded quads to support extended-edge sampling of each terrain tile into empty regions.
TerrainPaintUtility.DrawQuadPadded(terrainTile.clippedPCPixels, terrainTile.paddedPCPixels,
terrainTile.clippedTerrainPixels, terrainTile.paddedTerrainPixels, sourceTexture);
sourceTexture.filterMode = oldFilterMode;
afterBlit?.Invoke(terrainTile);
}
GL.PopMatrix();
RenderTexture.active = oldRenderTexture;
}
private void ScatterInternal(
Func<ITerrainInfo, RenderTexture> terrainToRT,
string operationName,
Material blitMaterial = null,
int blitPass = 0,
Action<ITerrainInfo> beforeBlit = null,
Action<ITerrainInfo> afterBlit = null)
{
var oldRT = RenderTexture.active;
if (blitMaterial == null)
blitMaterial = TerrainPaintUtility.GetBlitMaterial();
for (int i = 0; i < m_TerrainTiles.Count; i++)
{
TerrainTile terrainTile = m_TerrainTiles[i];
if (!terrainTile.scatterEnable)
continue;
RenderTexture target = terrainToRT(terrainTile);
if ((target == null) || (!terrainTile.scatterEnable)) // double check scatterEnable in case terrainToRT modified it
continue;
if ((target.width != targetTextureWidth) || (target.height != targetTextureHeight))
{
Debug.LogWarning(operationName + " requires the same resolution for all Terrains - mismatched Terrains are ignored.", terrainTile.terrain);
continue;
}
beforeBlit?.Invoke(terrainTile);
if (!terrainTile.scatterEnable) // check again, beforeBlit may have modified it
continue;
RenderTexture.active = target;
GL.PushMatrix();
GL.LoadPixelMatrix(0, target.width, 0, target.height);
{
FilterMode oldFilterMode = destinationRenderTexture.filterMode;
destinationRenderTexture.filterMode = FilterMode.Point;
blitMaterial.SetTexture("_MainTex", destinationRenderTexture);
blitMaterial.SetPass(blitPass);
TerrainPaintUtility.DrawQuad(terrainTile.clippedTerrainPixels, terrainTile.clippedPCPixels, destinationRenderTexture);
destinationRenderTexture.filterMode = oldFilterMode;
}
GL.PopMatrix();
afterBlit?.Invoke(terrainTile);
}
RenderTexture.active = oldRT;
}
public void Gather(Func<ITerrainInfo, Texture> terrainSource, Color defaultColor, Material blitMaterial = null, int blitPass = 0, Action<ITerrainInfo> beforeBlit = null, Action<ITerrainInfo> afterBlit = null)
{
if (terrainSource != null)
GatherInternal(terrainSource, defaultColor, "PaintContext.Gather", blitMaterial, blitPass, beforeBlit, afterBlit);
}
public void Scatter(Func<ITerrainInfo, RenderTexture> terrainDest, Material blitMaterial = null, int blitPass = 0, Action<ITerrainInfo> beforeBlit = null, Action<ITerrainInfo> afterBlit = null)
{
if (terrainDest != null)
ScatterInternal(terrainDest, "PaintContext.Scatter", blitMaterial, blitPass, beforeBlit, afterBlit);
}
public void GatherHeightmap()
{
var blitMaterial = TerrainPaintUtility.GetHeightBlitMaterial();
blitMaterial.SetFloat("_Height_Offset", 0.0f);
blitMaterial.SetFloat("_Height_Scale", 1.0f);
GatherInternal(
t => t.terrain.terrainData.heightmapTexture,
new Color(0.0f, 0.0f, 0.0f, 0.0f),
"PaintContext.GatherHeightmap",
blitMaterial: blitMaterial,
beforeBlit: t =>
{
blitMaterial.SetFloat("_Height_Offset", (t.terrain.GetPosition().y - heightWorldSpaceMin) / heightWorldSpaceSize * kNormalizedHeightScale);
blitMaterial.SetFloat("_Height_Scale", t.terrain.terrainData.size.y / heightWorldSpaceSize);
});
}
public void ScatterHeightmap(string editorUndoName)
{
var blitMaterial = TerrainPaintUtility.GetHeightBlitMaterial();
blitMaterial.SetFloat("_Height_Offset", 0.0f);
blitMaterial.SetFloat("_Height_Scale", 1.0f);
ScatterInternal(
t => t.terrain.terrainData.heightmapTexture,
"PaintContext.ScatterHeightmap",
blitMaterial: blitMaterial,
beforeBlit: t =>
{
onTerrainTileBeforePaint?.Invoke(t, ToolAction.PaintHeightmap, editorUndoName);
blitMaterial.SetFloat("_Height_Offset", (heightWorldSpaceMin - t.terrain.GetPosition().y) / t.terrain.terrainData.size.y * kNormalizedHeightScale);
blitMaterial.SetFloat("_Height_Scale", heightWorldSpaceSize / t.terrain.terrainData.size.y);
},
afterBlit: t =>
{
var syncMethod = t.terrain.drawInstanced ?
TerrainHeightmapSyncControl.None : //keep the data on the GPU while painting
TerrainHeightmapSyncControl.HeightAndLod; //sync the heightmaps and the lod info each frame (is important to sync the Lod info so performance stays reasonable)
t.terrain.terrainData.DirtyHeightmapRegion(t.clippedTerrainPixels, syncMethod);
OnTerrainPainted(t, ToolAction.PaintHeightmap);
});
}
public void GatherHoles()
{
GatherInternal(
t => t.terrain.terrainData.holesTexture,
new Color(0.0f, 0.0f, 0.0f, 0.0f),
"PaintContext.GatherHoles");
}
public void ScatterHoles(string editorUndoName)
{
ScatterInternal(
t =>
{
onTerrainTileBeforePaint?.Invoke(t, ToolAction.PaintHoles, editorUndoName);
t.terrain.terrainData.CopyActiveRenderTextureToTexture(TerrainData.HolesTextureName, 0, t.clippedPCPixels, t.clippedTerrainPixels.min, true);
OnTerrainPainted(t, ToolAction.PaintHoles);
return null;
},
"PaintContext.ScatterHoles");
}
public void GatherNormals()
{
GatherInternal(
t => t.terrain.normalmapTexture,
new Color(0.5f, 0.5f, 0.5f, 0.5f),
"PaintContext.GatherNormals");
}
private SplatmapUserData GetTerrainLayerUserData(ITerrainInfo context, TerrainLayer terrainLayer = null, bool addLayerIfDoesntExist = false)
{
// look up existing user data, if any
SplatmapUserData userData = (context.userData as SplatmapUserData);
if (userData != null)
{
// check if it is appropriate, return if so
if ((terrainLayer == null) || (terrainLayer == userData.terrainLayer))
return userData;
else
userData = null;
}
// otherwise let's build it
if (userData == null)
{
int tileLayerIndex = -1;
if (terrainLayer != null)
{
// look for the layer on the terrain
tileLayerIndex = TerrainPaintUtility.FindTerrainLayerIndex(context.terrain, terrainLayer);
if ((tileLayerIndex == -1) && (addLayerIfDoesntExist))
{
onTerrainTileBeforePaint?.Invoke(context, ToolAction.AddTerrainLayer, "Adding Terrain Layer");
tileLayerIndex = TerrainPaintUtility.AddTerrainLayer(context.terrain, terrainLayer);
}
}
// if we found the layer, build user data
if (tileLayerIndex != -1)
{
userData = new SplatmapUserData();
userData.terrainLayer = terrainLayer;
userData.terrainLayerIndex = tileLayerIndex;
userData.mapIndex = tileLayerIndex >> 2;
userData.channelIndex = tileLayerIndex & 0x3;
}
context.userData = userData;
}
return userData;
}
public void GatherAlphamap(TerrainLayer inputLayer, bool addLayerIfDoesntExist = true)
{
if (inputLayer == null)
return;
Material copyTerrainLayerMaterial = TerrainPaintUtility.GetCopyTerrainLayerMaterial();
Vector4[] layerMasks = { new Vector4(1, 0, 0, 0), new Vector4(0, 1, 0, 0), new Vector4(0, 0, 1, 0), new Vector4(0, 0, 0, 1) };
GatherInternal(
t =>
{ // return the texture to be gathered from this terrain tile
SplatmapUserData userData = GetTerrainLayerUserData(t, inputLayer, addLayerIfDoesntExist);
if (userData != null)
return TerrainPaintUtility.GetTerrainAlphaMapChecked(t.terrain, userData.mapIndex);
else
return null;
},
new Color(0.0f, 0.0f, 0.0f, 0.0f),
"PaintContext.GatherAlphamap",
copyTerrainLayerMaterial, 0,
t =>
{ // before blit -- setup layer mask in the material
SplatmapUserData userData = GetTerrainLayerUserData(t);
if (userData == null)
return;
copyTerrainLayerMaterial.SetVector("_LayerMask", layerMasks[userData.channelIndex]);
});
}
public void ScatterAlphamap(string editorUndoName)
{
Vector4[] layerMasks = { new Vector4(1, 0, 0, 0), new Vector4(0, 1, 0, 0), new Vector4(0, 0, 1, 0), new Vector4(0, 0, 0, 1) };
Material copyTerrainLayerMaterial = TerrainPaintUtility.GetCopyTerrainLayerMaterial();
var rtdesc = new RenderTextureDescriptor(destinationRenderTexture.width, destinationRenderTexture.height, GraphicsFormat.R8G8B8A8_UNorm, GraphicsFormat.None);
rtdesc.sRGB = false;
rtdesc.useMipMap = false;
rtdesc.autoGenerateMips = false;
RenderTexture tempTarget = RenderTexture.GetTemporary(rtdesc);
ScatterInternal(
t => // We're going to do ALL of the work in this terrainToRT function, as it is very custom, and we'll just return null to skip the ScatterInternal rendering
{
SplatmapUserData userData = GetTerrainLayerUserData(t);
if (userData != null)
{
onTerrainTileBeforePaint?.Invoke(t, ToolAction.PaintTexture, editorUndoName);
int targetAlphamapIndex = userData.mapIndex;
int targetChannelIndex = userData.channelIndex;
Texture2D targetAlphamapTexture = t.terrain.terrainData.alphamapTextures[targetAlphamapIndex];
destinationRenderTexture.filterMode = FilterMode.Point;
sourceRenderTexture.filterMode = FilterMode.Point;
// iterate all alphamaps to modify them (have to modify all to renormalize)
for (int i = 0; i <= t.terrain.terrainData.alphamapTextureCount; i++) // NOTE: this is a non-standard for loop
{
// modify the target index last, (skip it the first time)
if (i == targetAlphamapIndex)
continue;
int alphamapIndex = (i == t.terrain.terrainData.alphamapTextureCount) ? targetAlphamapIndex : i;
Texture2D alphamapTexture = t.terrain.terrainData.alphamapTextures[alphamapIndex];
if ((alphamapTexture.width != targetTextureWidth) || (alphamapTexture.height != targetTextureHeight))
{
Debug.LogWarning("PaintContext alphamap operations must use the same resolution for all Terrains - mismatched Terrains are ignored.", t.terrain);
continue;
}
RenderTexture.active = tempTarget;
GL.PushMatrix();
GL.LoadPixelMatrix(0, tempTarget.width, 0, tempTarget.height);
{
copyTerrainLayerMaterial.SetTexture("_MainTex", destinationRenderTexture);
copyTerrainLayerMaterial.SetTexture("_OldAlphaMapTexture", sourceRenderTexture);
copyTerrainLayerMaterial.SetTexture("_OriginalTargetAlphaMap", targetAlphamapTexture);
copyTerrainLayerMaterial.SetTexture("_AlphaMapTexture", alphamapTexture);
copyTerrainLayerMaterial.SetVector("_LayerMask", alphamapIndex == targetAlphamapIndex ? layerMasks[targetChannelIndex] : Vector4.zero);
copyTerrainLayerMaterial.SetVector("_OriginalTargetAlphaMask", layerMasks[targetChannelIndex]);
copyTerrainLayerMaterial.SetPass(1);
TerrainPaintUtility.DrawQuad2(t.clippedPCPixels, t.clippedPCPixels, destinationRenderTexture, t.clippedTerrainPixels, alphamapTexture);
}
GL.PopMatrix();
t.terrain.terrainData.CopyActiveRenderTextureToTexture(TerrainData.AlphamapTextureName, alphamapIndex, t.clippedPCPixels, t.clippedTerrainPixels.min, true);
}
RenderTexture.active = null;
OnTerrainPainted(t, ToolAction.PaintTexture);
}
return null;
},
"PaintContext.ScatterAlphamap",
copyTerrainLayerMaterial, 0);
RenderTexture.ReleaseTemporary(tempTarget);
}
// Collects modified terrain so that we can update some deferred operations at the mouse up event
private struct PaintedTerrain
{
public Terrain terrain;
public ToolAction action;
}
private static List<PaintedTerrain> s_PaintedTerrain = new List<PaintedTerrain>();
private static void OnTerrainPainted(ITerrainInfo tile, ToolAction action)
{
for (int i = 0; i < s_PaintedTerrain.Count; ++i)
{
if (tile.terrain == s_PaintedTerrain[i].terrain)
{
var pt = s_PaintedTerrain[i]; // round-about assignment here because of struct copy semantics
pt.action |= action;
s_PaintedTerrain[i] = pt;
return;
}
}
s_PaintedTerrain.Add(new PaintedTerrain { terrain = tile.terrain, action = action });
}
public static void ApplyDelayedActions()
{
for (int i = 0; i < s_PaintedTerrain.Count; ++i)
{
var pt = s_PaintedTerrain[i];
var terrainData = pt.terrain.terrainData;
if (terrainData == null)
continue;
if ((pt.action & ToolAction.PaintHeightmap) != 0)
{
terrainData.SyncHeightmap();
}
if ((pt.action & ToolAction.PaintHoles) != 0)
{
terrainData.SyncTexture(TerrainData.HolesTextureName);
}
if ((pt.action & ToolAction.PaintTexture) != 0)
{
terrainData.SetBaseMapDirty();
terrainData.SyncTexture(TerrainData.AlphamapTextureName);
}
pt.terrain.editorRenderFlags = TerrainRenderFlags.All;
}
s_PaintedTerrain.Clear();
}
}
}