s&box Package Code Search

Search C# source code, UI razor templates, shaders, and configs across s&box packages.

Showing code results for query: * (14 total matches found)
kivin.performantterrainscatterer / .obj/__compiler_extra.cs
Game library
global using static Sandbox.Internal.GlobalGameNamespace;
global using Microsoft.AspNetCore.Components;
global using Microsoft.AspNetCore.Components.Rendering;
[assembly: global::System.Reflection.AssemblyMetadata( "AddonTitle", "Performant Terrain Scatterer" )]
[assembly: global::System.Reflection.AssemblyMetadata( "AddonIdent", "performantterrainscatterer" )]
[assembly: global::System.Reflection.AssemblyMetadata( "OrgIdent", "kivin" )]
[assembly: global::System.Reflection.AssemblyMetadata( "Ident", "kivin.performantterrainscatterer" )]
[assembly: global::System.Reflection.AssemblyMetadata( "EngineVersion", "26" )]
[assembly: global::System.Reflection.AssemblyMetadata( "EngineMinorVersion", "1" )]

[assembly: System.Runtime.Versioning.TargetFramework( ".NETCoreApp,Version=v9.0", FrameworkDisplayName = ".NET 9.0" )]
[assembly: global::System.Reflection.AssemblyMetadata( "CompileTime", "2026-06-23T11:51:31.4189525Z" )]
[assembly: global::System.Reflection.AssemblyVersion("0.0.119.0")]
[assembly: global::System.Reflection.AssemblyFileVersion("0.0.119.0")]
kivin.performantterrainscatterer / PerformantTerrainScatterer/PerformantTerrainScattererManager.Chunks.cs
Game library
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Threading.Tasks;
using Sandbox;

public sealed partial class PerformantTerrainScatterer
{
	private void ManageChunks()
	{
		if ( Scene == null ) return;
		var camera = Scene.Camera;
		if ( !camera.IsValid() ) return;
		Vector3 cameraPos = camera.WorldPosition;

		if ( _activeChunks.Count > 0 && cameraPos.DistanceSquared( _lastCameraPos ) < CameraUpdateThresholdSq )
			return;

		_lastCameraPos = cameraPos;
		int cameraCx = (int)MathF.Floor( cameraPos.x / ChunkSize );
		int cameraCy = (int)MathF.Floor( cameraPos.y / ChunkSize );
		int chunksInRadius = (int)MathF.Ceiling( ChunkLoadDistance / ChunkSize );

		for ( int dx = -chunksInRadius; dx <= chunksInRadius; dx++ )
		{
			for ( int dy = -chunksInRadius; dy <= chunksInRadius; dy++ )
			{
				var chunkCoord = (cameraCx + dx, cameraCy + dy);
				if ( !_activeChunks.ContainsKey( chunkCoord ) && _generatingChunks.Add( chunkCoord ) )
					_ = GenerateChunkAsync( chunkCoord.Item1, chunkCoord.Item2 );
			}
		}

		_chunkRemovalList.Clear();
		foreach ( var key in _activeChunks.Keys )
		{
			if ( Math.Abs( key.Item1 - cameraCx ) > chunksInRadius ||
			     Math.Abs( key.Item2 - cameraCy ) > chunksInRadius )
				_chunkRemovalList.Add( key );
		}

		foreach ( var t in _chunkRemovalList )
			_activeChunks.Remove( t );

		if ( _chunkRemovalList.Count > 0 )
			RebuildRenderCache();
	}

	private async Task GenerateChunkAsync( int cx, int cy )
	{
		try
		{
			if ( !IsValid || Scene == null ) return;
			Transform terrainTransform = TargetTerrain.IsValid() ? TargetTerrain.WorldTransform : new Transform();

			var tentativeInstances =
				await GameTask.RunInThreadAsync( () => CalculateChunkPlacements( cx, cy, terrainTransform ) );

			if ( !IsValid || Scene == null ) return;

			var modelEntries = ModelEntries;
			if ( tentativeInstances == null || modelEntries == null ) return;

			int cellsPerChunk = (int)MathF.Ceiling( ChunkSize / GridCellSize );
			int gridPadding = ObstructionBuffer;
			int gridWidth = cellsPerChunk + (gridPadding * 2) + 2;
			int gridArea = gridWidth * gridWidth;

			bool[] localBlockedGrid = ArrayPool<bool>.Shared.Rent( gridArea );
			Array.Clear( localBlockedGrid, 0, gridArea );

			try
			{
				int minGridX = (int)MathF.Floor( (cx * ChunkSize) / GridCellSize ) - gridPadding;
				int minGridY = (int)MathF.Floor( (cy * ChunkSize) / GridCellSize ) - gridPadding;
				int traceCounter = 0;
				bool needsTrace = CheckObstructions || SnapToGround;
				List<Transform>[] modelLists = new List<Transform>[modelEntries.Length];
				float cxSum = 0f, cySum = 0f, czSum = 0f;
				int placedCount = 0;
				Vector3 traceUpOffset = Vector3.Up * TraceStartHeight;

				foreach ( var inst in tentativeInstances )
				{
					if ( !IsValid || Scene == null ) return;

					Vector3 finalPosition = inst.Position;
					int gridX = (int)MathF.Floor( inst.Position.x / GridCellSize );
					int gridY = (int)MathF.Floor( inst.Position.y / GridCellSize );
					int localX = gridX - minGridX;
					int localY = gridY - minGridY;

					if ( CheckObstructions )
					{
						bool isBlocked = false;
						for ( int dx = -ObstructionBuffer; dx <= ObstructionBuffer; dx++ )
						{
							int nx = localX + dx;
							if ( nx < 0 || nx >= gridWidth ) continue;
							for ( int dy = -ObstructionBuffer; dy <= ObstructionBuffer; dy++ )
							{
								int ny = localY + dy;
								if ( ny < 0 || ny >= gridWidth ) continue;
								if ( localBlockedGrid[nx + ny * gridWidth] )
								{
									isBlocked = true;
									break;
								}
							}

							if ( isBlocked ) break;
						}

						if ( isBlocked ) continue;
					}

					if ( needsTrace )
					{
						var traceStart = inst.Position + traceUpOffset;
						var traceEnd = inst.Position - traceUpOffset;
						var trace = Scene.Trace.Ray( traceStart, traceEnd )
							.WithoutTags( TraceIgnoreTags )
							.Run();

						bool hitObstruction = trace.Hit && IsObstruction( trace.GameObject );

						if ( CheckObstructions && (trace.StartedSolid || hitObstruction) )
						{
							if ( localX >= 0 && localX < gridWidth && localY >= 0 && localY < gridWidth )
								localBlockedGrid[localX + localY * gridWidth] = true;
							continue;
						}

						if ( SnapToGround && trace.Hit && !trace.StartedSolid && !hitObstruction )
							finalPosition = trace.HitPosition;

						traceCounter++;
						if ( traceCounter % 100 == 0 )
						{
							await GameTask.Yield();
							if ( !IsValid || Scene == null ) return;
						}
					}

					if ( inst.ModelIndex < 0 || inst.ModelIndex >= modelEntries.Length ) continue;

					finalPosition += Vector3.Up * modelEntries[inst.ModelIndex].ZOffset;
					var list = modelLists[inst.ModelIndex] ??= new List<Transform>();
					list.Add( new Transform( finalPosition, inst.Rotation, inst.Scale ) );

					cxSum += finalPosition.x;
					cySum += finalPosition.y;
					czSum += finalPosition.z;
					placedCount++;
				}

				if ( !IsValid ) return;

				var chunk = new ClutterChunk();
				chunk.Center = placedCount > 0
					? new Vector3( cxSum / placedCount, cySum / placedCount, czSum / placedCount )
					: new Vector3( cx * ChunkSize + ChunkSize * 0.5f, cy * ChunkSize + ChunkSize * 0.5f, 0f );

				for ( int i = 0; i < modelLists.Length; i++ )
				{
					if ( modelLists[i] != null && modelLists[i].Count > 0 )
						chunk.TransformsByModel[i] = modelLists[i].ToArray();
				}

				_activeChunks[(cx, cy)] = chunk;
				RebuildRenderCache();
			}
			finally
			{
				ArrayPool<bool>.Shared.Return( localBlockedGrid );
			}
		}
		catch ( Exception )
		{
		}
		finally
		{
			_generatingChunks.Remove( (cx, cy) );
		}
	}
}
kivin.performantterrainscatterer / UnitTests/UnitTest.cs
UnitTest library
global using Microsoft.VisualStudio.TestTools.UnitTesting;

[TestClass]
public class TestInit
{
	public static Sandbox.TestAppSystem AppSystem;

	[AssemblyInitialize]
	public static void AssemblyInitialize( TestContext context )
	{
		AppSystem = new Sandbox.TestAppSystem();
		AppSystem.Init();
	}

	[AssemblyCleanup]
	public static void AssemblyCleanup()
	{
		AppSystem.Shutdown();
	}
}
kivin.performantterrainscatterer / PerformantTerrainScatterer/PerformantTerrainScattererManager.cs
Game library
using System;
using System.Collections.Generic;
using Sandbox;

public sealed partial class PerformantTerrainScatterer : Component, Component.ExecuteInEditor
{
	[Property, Category( "Terrain" ), Change( nameof(InitializeSystem) )]
	public Terrain TargetTerrain { get; set; }

	[Property, Category( "Models" ), Change( nameof(InitializeSystem) )]
	public List<ScattererModelEntry> Models { get; set; } = new();

	[Property, Category( "Terrain" ), Change( nameof(InitializeSystem) ), Range( 0, 90 )]
	public float MaxSlopeAngle { get; set; } = 45f;

	[Property, Category( "Terrain" ), Change( nameof(InitializeSystem) )]
	public bool ReduceDensityOnTransitions { get; set; } = false;

	[Property, Category( "Terrain" ), Change( nameof(InitializeSystem) ),
	 ShowIf( nameof(ReduceDensityOnTransitions), true )]
	public float TransitionThinningIntensity { get; set; } = 1.0f;

	[Property, Category( "Terrain" ), Change( nameof(InitializeSystem) )]
	public bool SnapToGround { get; set; } = true;

	[Property, Category( "Collision" ), Change( nameof(InitializeSystem) )]
	public bool CheckObstructions { get; set; } = true;

	[Property, Category( "Collision" ), Change( nameof(InitializeSystem) )]
	public float TraceStartHeight { get; set; } = 200f;

	[Property, Category( "Collision" ), Change( nameof(InitializeSystem) )]
	public float GridCellSize { get; set; } = 50f;

	[Property, Category( "Collision" ), Change( nameof(InitializeSystem) )]
	public int ObstructionBuffer { get; set; } = 2;

	[Property, Category( "Collision" )]
	public string[] TraceIgnoreTags { get; set; } = { "player", "trigger", "clutter" };

	[Property, Category( "Performance" ), Change( nameof(InitializeSystem) )]
	public int MaxInstancesPerChunk { get; set; } = 500;

	[Property, Category( "Performance" ), Change( nameof(InitializeSystem) )]
	public List<MaterialInstanceOverride> MaterialOverrides { get; set; } = new();

	[Property, Category( "Performance" ), Change( nameof(InitializeSystem) )]
	public float ChunkSize { get; set; } = 500f;

	[Property, Category( "Performance" ), Change( nameof(InitializeSystem) )]
	public float ChunkLoadDistance { get; set; } = 3000f;

	[Property, Category( "Performance" )] public bool UseFrustumCulling { get; set; } = true;
	[Property, Category( "Performance" )] public float FrustumCullMinDistance { get; set; } = 800f;
	[Property, Category( "Performance" )] public bool AutomaticallyInitializeSystem { get; set; } = true;

	[Property, Category( "Terrain" ), Change( nameof(InitializeSystem) )]
	public bool UseTerrainNormal { get; set; } = true;

	[Property, Category( "Seed" ), Change( nameof(InitializeSystem) )]
	public int Seed { get; set; } = 12345;

	[Property, Category( "Randomization" ), Change( nameof(InitializeSystem) )]
	public bool RandomizeRotation { get; set; } = true;

	private readonly Dictionary<(int, int), ClutterChunk> _activeChunks = new();
	private readonly HashSet<(int, int)> _generatingChunks = new();
	private readonly List<(int, int)> _chunkRemovalList = new();

	private ushort[] _heightMapPixels;
	private uint[] _controlMapPixels;
	private int _resolution;
	private float _terrainSize;
	private float _terrainHeight;
	private float _maxRenderDistance;
	private float[][] _modelMaterialWeights;
	private GameObject _cachedHitObject;
	private bool _cachedIsObstruction;
	private Sandbox.Utility.INoiseField[] _modelNoiseFields;
	private Vector3 _lastCameraPos;
	private const float CameraUpdateThresholdSq = 10000f;
	private const float UshortMaxReciprocal = 1.0f / ushort.MaxValue;
	private SceneCustomObject _sceneObject;

	private ScattererModelEntry[] ModelEntries { get; set; }
	private float[] _modelRenderDistSq;
	private int[] _modelLodCounts;
	private float[][] _modelLodDistancesSq;

	private int[] _materialMaxInstances;
	private int _maxInstancesLoopCount;

	private ChunkRenderData[] _renderCache = Array.Empty<ChunkRenderData>();

	[ThreadStatic] private static List<Transform>[] _lodBuckets;

	protected override void OnEnabled()
	{
		if ( !TargetTerrain.IsValid() )
		{
			TargetTerrain = Scene?.GetComponentInChildren<Terrain>();
		}

		CreateSceneObject();
		if ( AutomaticallyInitializeSystem )
		{
			InitializeSystem();
		}
	}


	protected override void OnUpdate()
	{
		ManageChunks();
	}

	protected override void OnDisabled()
	{
		_sceneObject?.Delete();
		_sceneObject = null;
	}

	private void CreateSceneObject()
	{
		_sceneObject?.Delete();
		if ( Scene == null ) return;

		_sceneObject = new SceneCustomObject( Scene.SceneWorld )
		{
			RenderOverride = RenderClutter,
			Transform = new Transform( Vector3.Zero, Rotation.Identity, 1f ),
			Flags = { IsOpaque = true, IsTranslucent = false }
		};
	}

	[Button( "Reinitialize" )]
	public void InitializeSystem()
	{
		_activeChunks.Clear();
		_generatingChunks.Clear();
		_chunkRemovalList.Clear();
		_renderCache = Array.Empty<ChunkRenderData>();
		ModelEntries = null;
		_modelRenderDistSq = null;
		_modelLodCounts = null;
		_modelLodDistancesSq = null;

		if ( Models == null || Models.Count == 0 ) return;
		if ( !TargetTerrain.IsValid() || TargetTerrain.Storage == null ) return;

		// --- CONFIGURATION VALIDATION WARNINGS ---
		var seenModels = new HashSet<Model>();
		foreach ( var entry in Models )
		{
			if ( entry.Model != null && !seenModels.Add( entry.Model ) )
			{
				Log.Warning( $"[Scatterer] Duplicate model entry found for '{entry.Model.ResourceName}' in the Models configuration list." );
			}
		}

		if ( MaterialOverrides != null )
		{
			var seenOverrideMaterials = new HashSet<TerrainMaterial>();
			foreach ( var ov in MaterialOverrides )
			{
				if ( ov.Material != null && !seenOverrideMaterials.Add( ov.Material ) )
				{
					Log.Warning( $"[Scatterer] Duplicate material override configuration found for material '{ov.Material.ResourceName}'." );
				}
			}
		}

		bool hasValidModel = false;
		_maxRenderDistance = ChunkLoadDistance;

		foreach ( var t in Models )
		{
			if ( t.Model != null )
			{
				var seenMaterials = new HashSet<TerrainMaterial>();
				bool hasWeight = false;

				// MISSING NULL CHECK FIXED HERE:
				if ( t.MaterialWeights != null )
				{
					foreach ( var mw in t.MaterialWeights )
					{
						if ( mw.Material == null ) continue;

						if ( !seenMaterials.Add( mw.Material ) )
							Log.Warning(
								$"[Scatterer] Duplicate material entry '{mw.Material.ResourceName}' found for model '{t.Model.ResourceName}'." );

						if ( mw.Weight <= 0f )
							Log.Warning(
								$"[Scatterer] Weight is set to 0 for material '{mw.Material.ResourceName}' on model '{t.Model.ResourceName}'." );

						if ( mw.Weight > 0f ) hasWeight = true;
					}
				}

				if ( hasWeight ) hasValidModel = true;

				if ( t.RenderDistance > _maxRenderDistance )
					_maxRenderDistance = t.RenderDistance;
			}
		}

		if ( !hasValidModel ) return;

		ModelEntries = Models.ToArray();
		_modelRenderDistSq = new float[ModelEntries.Length];
		_modelLodCounts = new int[ModelEntries.Length];
		_modelLodDistancesSq = new float[ModelEntries.Length][];

		for ( int m = 0; m < ModelEntries.Length; m++ )
		{
			_modelRenderDistSq[m] = ModelEntries[m].RenderDistance * ModelEntries[m].RenderDistance;
			var mdl = ModelEntries[m].Model;

			if ( mdl != null )
			{
				_modelLodCounts[m] = Math.Max( 1, mdl.MeshInfo.LodCount );
				if ( mdl.MeshInfo.LodSwitchDistances != null && mdl.MeshInfo.LodSwitchDistances.Length > 0 &&
				     mdl.MeshInfo.LodSwitchDistances[0] > 0f )
				{
					var switchDistances = mdl.MeshInfo.LodSwitchDistances;
					_modelLodDistancesSq[m] = new float[switchDistances.Length];
					for ( int i = 0; i < switchDistances.Length; i++ )
					{
						float dist = switchDistances[i] * ModelEntries[m].LodBias;
						_modelLodDistancesSq[m][i] = dist * dist;
					}
				}
				else if ( _modelLodCounts[m] > 1 )
				{
					int switchCount = _modelLodCounts[m] - 1;
					_modelLodDistancesSq[m] = new float[switchCount];
					float step = ModelEntries[m].RenderDistance / _modelLodCounts[m];
					for ( int i = 0; i < switchCount; i++ )
					{
						float dist = (step * (i + 1)) * ModelEntries[m].LodBias;
						_modelLodDistancesSq[m][i] = dist * dist;
					}
				}
				else
				{
					_modelLodDistancesSq[m] = Array.Empty<float>();
				}
			}
			else
			{
				_modelLodCounts[m] = 1;
				_modelLodDistancesSq[m] = Array.Empty<float>();
			}
		}

		_heightMapPixels = TargetTerrain.Storage.HeightMap;
		_controlMapPixels = TargetTerrain.Storage.ControlMap;
		_resolution = TargetTerrain.Storage.Resolution;
		_terrainSize = TargetTerrain.TerrainSize;
		_terrainHeight = TargetTerrain.TerrainHeight;

		_modelMaterialWeights = new float[Models.Count][];

		for ( int m = 0; m < Models.Count; m++ )
		{
			_modelMaterialWeights[m] = new float[32];
			if ( TargetTerrain.Storage.Materials != null && Models[m].MaterialWeights != null )
			{
				for ( int i = 0; i < TargetTerrain.Storage.Materials.Count; i++ )
				{
					foreach ( var mw in Models[m].MaterialWeights )
					{
						if ( mw.Material == TargetTerrain.Storage.Materials[i] )
						{
							_modelMaterialWeights[m][i] = mw.Weight;
							break;
						}
					}
				}
			}
		}

		_modelNoiseFields = new Sandbox.Utility.INoiseField[Models.Count];
		for ( int m = 0; m < Models.Count; m++ )
		{
			var modelEntry = Models[m];
			if ( modelEntry.NoiseType == ScattererNoiseType.None ) continue;
			var parameters = new Sandbox.Utility.Noise.Parameters( Seed + m, modelEntry.NoiseScale );

			if ( modelEntry.NoiseType == ScattererNoiseType.Perlin )
				_modelNoiseFields[m] = Sandbox.Utility.Noise.PerlinField( parameters );
			else if ( modelEntry.NoiseType == ScattererNoiseType.Simplex )
				_modelNoiseFields[m] = Sandbox.Utility.Noise.SimplexField( parameters );
		}

		// --- MAPPING AND CEILING INITIALIZATION ---
		_materialMaxInstances = new int[32];
		Array.Fill( _materialMaxInstances, MaxInstancesPerChunk );
		_maxInstancesLoopCount = MaxInstancesPerChunk;

		if ( TargetTerrain.Storage.Materials != null && MaterialOverrides != null )
		{
			for ( int i = 0; i < TargetTerrain.Storage.Materials.Count; i++ )
			{
				foreach ( var ov in MaterialOverrides )
				{
					if ( ov.Material == TargetTerrain.Storage.Materials[i] )
					{
						_materialMaxInstances[i] = ov.MaxInstances;
						if ( ov.MaxInstances > _maxInstancesLoopCount )
						{
							_maxInstancesLoopCount = ov.MaxInstances;
						}
						break;
					}
				}
			}
		}

		RebuildRenderCache();
	}
}
kivin.performantterrainscatterer / PerformantTerrainScatterer/PerformantTerrainScattererManager.Data.cs
Game library
using System.Collections.Generic;
using Sandbox;

public sealed partial class PerformantTerrainScatterer
{
	public enum ScattererNoiseType
	{
		None,
		Perlin,
		Simplex
	}

	public struct MaterialWeight
	{
		public MaterialWeight() { }
		[Property] public TerrainMaterial Material { get; set; } = null;
		[Property, Range( 0f, 100f )] public float Weight { get; set; } = 1f;
	}

	public struct MaterialInstanceOverride
	{
		public MaterialInstanceOverride() { }
		[Property] public TerrainMaterial Material { get; set; } = null;
		[Property] public int MaxInstances { get; set; } = 500;
	}

	public struct ScattererModelEntry
	{
		public ScattererModelEntry() { }
		[Property, Category( "Base" )] public Model Model { get; set; } = null;
		[Property, Category( "Base" )] public float ZOffset { get; set; } = 0f;

		[Property, Category( "Noise Scattering" )]
		public ScattererNoiseType NoiseType { get; set; } = ScattererNoiseType.None;

		[Property, Category( "Noise Scattering" )]
		public float NoiseScale { get; set; } = 0.002f;

		[Property, Category( "Noise Scattering" ), Range( 0f, 1f )]
		public float NoiseThreshold { get; set; } = 0.4f;

		[Property, Category( "Performance" )] public float RenderDistance { get; set; } = 2500f;
		[Property, Category( "Terrain" )] public List<MaterialWeight> MaterialWeights { get; set; } = new();

		[Property, Category( "Randomization" )]
		public bool RandomizeScale { get; set; } = false;

		[Property, Category( "Randomization" ), ShowIf( nameof(RandomizeScale), true )]
		public float MinScale { get; set; } = 0.8f;

		[Property, Category( "Randomization" ), ShowIf( nameof(RandomizeScale), true )]
		public float MaxScale { get; set; } = 1.2f;

		[Property, Category( "LOD" ), Range( 0.1f, 4f )]
		public float LodBias { get; set; } = 1.0f;
	}

	private struct ChunkRenderData
	{
		public Vector3 Center;
		public ModelRenderBatch[] Batches;
	}

	private struct ModelRenderBatch
	{
		public Model Model;
		public Transform[] Transforms;
		public float RenderDistSq;
		public float[] LodDistancesSq;
		public int MaxLod;
	}

	private struct InstanceData
	{
		public Vector3 Position;
		public Rotation Rotation;
		public float Scale;
		public int ModelIndex;
	}

	private class ClutterChunk
	{
		public Vector3 Center;
		public readonly Dictionary<int, Transform[]> TransformsByModel = new();
	}
}
kivin.performantterrainscatterer / PerformantTerrainScatterer/PerformantTerrainScattererManager.Rendering.cs
Game library
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Sandbox;

public sealed partial class PerformantTerrainScatterer
{
	private void RebuildRenderCache()
	{
		var newCache = new ChunkRenderData[_activeChunks.Count];
		int index = 0;

		foreach ( var chunk in _activeChunks.Values )
		{
			var batches = new ModelRenderBatch[chunk.TransformsByModel.Count];
			int bIndex = 0;

			foreach ( var kvp in chunk.TransformsByModel )
			{
				int mIdx = kvp.Key;
				batches[bIndex++] = new ModelRenderBatch
				{
					Model = ModelEntries[mIdx].Model,
					Transforms = kvp.Value,
					RenderDistSq = _modelRenderDistSq[mIdx],
					LodDistancesSq = _modelLodDistancesSq[mIdx],
					MaxLod = Math.Max( 0, _modelLodCounts[mIdx] - 1 )
				};
			}

			newCache[index++] = new ChunkRenderData { Center = chunk.Center, Batches = batches };
		}

		_renderCache = newCache;
	}

	private void RenderClutter( SceneObject obj )
	{
		var cache = _renderCache;
		if ( cache.Length == 0 ) return;

		if ( Scene == null ) return;
		var camera = Scene.Camera;
		if ( !camera.IsValid() ) return;

		Vector3 cameraPos = camera.WorldPosition;
		Vector3 cameraForward = camera.WorldRotation.Forward;

		float frustumMinDistSq = FrustumCullMinDistance * FrustumCullMinDistance;
		float maxRenderDistSq = _maxRenderDistance * _maxRenderDistance;
		bool useFrustum = UseFrustumCulling;

		if ( _lodBuckets == null )
		{
			_lodBuckets = new List<Transform>[16];
			for ( int i = 0; i < _lodBuckets.Length; i++ )
				_lodBuckets[i] = new List<Transform>();
		}

		for ( int c = 0; c < cache.Length; c++ )
		{
			ref var chunk = ref cache[c];

			Vector3 toChunk = chunk.Center - cameraPos;
			float chunkDistSq = toChunk.LengthSquared;

			if ( chunkDistSq > maxRenderDistSq ) continue;

			if ( useFrustum && chunkDistSq > frustumMinDistSq )
			{
				float dot = Vector3.Dot( cameraForward, toChunk );
				if ( dot < 0f && (dot * dot) > 0.04f * chunkDistSq )
					continue;
			}

			var batches = chunk.Batches;
			for ( int b = 0; b < batches.Length; b++ )
			{
				ref var batch = ref batches[b];
				if ( chunkDistSq > batch.RenderDistSq ) continue;

				int maxLod = batch.MaxLod;

				for ( int i = 0; i <= maxLod; i++ )
					_lodBuckets[i].Clear();

				var transforms = batch.Transforms;
				var lodDists = batch.LodDistancesSq;

				for ( int i = 0; i < transforms.Length; i++ )
				{
					Vector3 instPos = transforms[i].Position;
					float instDistSq = (instPos - cameraPos).LengthSquared;

					if ( instDistSq > batch.RenderDistSq ) continue;

					int lod = 0;
					for ( int l = 0; l < lodDists.Length; l++ )
					{
						if ( instDistSq > lodDists[l] )
							lod = l + 1;
						else
							break;
					}

					if ( lod > maxLod ) lod = maxLod;

					_lodBuckets[lod].Add( transforms[i] );
				}

				for ( int l = 0; l <= maxLod; l++ )
				{
					var bucket = _lodBuckets[l];
					if ( bucket.Count > 0 )
					{
						Graphics.DrawModelInstanced( batch.Model, CollectionsMarshal.AsSpan( bucket ), l );
					}
				}
			}
		}
	}
}
kivin.performantterrainscatterer / Editor/MyEditorMenu.cs
Editor library
using Editor;

public static class MyEditorMenu
{
	[Menu( "Editor", "Performant Terrain Scatterer/My Menu Option" )]
	public static void OpenMyMenu()
	{
		EditorUtility.DisplayDialog( "It worked!", "This is being called from your library's editor code!" );
	}
}
kivin.performantterrainscatterer / Code/PerformantTerrainScatterer/PerformantTerrainScattererManager.Data.cs
Game library
using System.Collections.Generic;
using Sandbox;

public sealed partial class PerformantTerrainScatterer
{
	public enum ScattererNoiseType
	{
		None,
		Perlin,
		Simplex
	}

	public struct MaterialWeight
	{
		public MaterialWeight() { }
		[Property] public TerrainMaterial Material { get; set; } = null;
		[Property, Range( 0f, 100f )] public float Weight { get; set; } = 1f;
	}

	public struct MaterialInstanceOverride
	{
		public MaterialInstanceOverride() { }
		[Property] public TerrainMaterial Material { get; set; } = null;
		[Property] public int MaxInstances { get; set; } = 500;
	}

	public struct ScattererModelEntry
	{
		public ScattererModelEntry() { }
		[Property, Category( "Base" )] public Model Model { get; set; } = null;
		[Property, Category( "Base" )] public float ZOffset { get; set; } = 0f;

		[Property, Category( "Noise Scattering" )]
		public ScattererNoiseType NoiseType { get; set; } = ScattererNoiseType.None;

		[Property, Category( "Noise Scattering" )]
		public float NoiseScale { get; set; } = 0.002f;

		[Property, Category( "Noise Scattering" ), Range( 0f, 1f )]
		public float NoiseThreshold { get; set; } = 0.4f;

		[Property, Category( "Performance" )] public float RenderDistance { get; set; } = 2500f;
		[Property, Category( "Terrain" )] public List<MaterialWeight> MaterialWeights { get; set; } = new();

		[Property, Category( "Randomization" )]
		public bool RandomizeScale { get; set; } = false;

		[Property, Category( "Randomization" ), ShowIf( nameof(RandomizeScale), true )]
		public float MinScale { get; set; } = 0.8f;

		[Property, Category( "Randomization" ), ShowIf( nameof(RandomizeScale), true )]
		public float MaxScale { get; set; } = 1.2f;

		[Property, Category( "LOD" ), Range( 0.1f, 4f )]
		public float LodBias { get; set; } = 1.0f;
	}

	private struct ChunkRenderData
	{
		public Vector3 Center;
		public ModelRenderBatch[] Batches;
	}

	private struct ModelRenderBatch
	{
		public Model Model;
		public Transform[] Transforms;
		public float RenderDistSq;
		public float[] LodDistancesSq;
		public int MaxLod;
	}

	private struct InstanceData
	{
		public Vector3 Position;
		public Rotation Rotation;
		public float Scale;
		public int ModelIndex;
	}

	private class ClutterChunk
	{
		public Vector3 Center;
		public readonly Dictionary<int, Transform[]> TransformsByModel = new();
	}
}
kivin.performantterrainscatterer / PerformantTerrainScatterer/PerformantTerrainScattererManager.Placement.cs
Game library
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Sandbox;

public sealed partial class PerformantTerrainScatterer
{
	private List<InstanceData> CalculateChunkPlacements( int cx, int cy, Transform terrainTransform )
	{
		if ( _heightMapPixels == null || _controlMapPixels == null ) return null;
		var modelEntries = ModelEntries;
		if ( modelEntries == null || modelEntries.Length == 0 ) return null;

		int loopCount = _maxInstancesLoopCount;
		var instances = new List<InstanceData>( loopCount );
		int chunkSeed = HashCombine( Seed, HashCombine( cx, cy ) );
		var random = new Random( chunkSeed );
		float textureEps = 1.0f / (_resolution - 1);
		float chunkBaseX = cx * ChunkSize;
		float chunkBaseY = cy * ChunkSize;
		int modelsCount = modelEntries.Length;

		int[] placedCountPerMaterial = new int[32];

		for ( int i = 0; i < loopCount; i++ )
		{
			float localX = random.NextSingle() * ChunkSize;
			float localY = random.NextSingle() * ChunkSize;
			float worldX = chunkBaseX + localX;
			float worldY = chunkBaseY + localY;

			Vector3 worldAbsPoint = new Vector3( worldX, worldY, 0f );
			Vector3 terrainLocal = terrainTransform.PointToLocal( worldAbsPoint );
			float u = terrainLocal.x / _terrainSize;
			float v = terrainLocal.y / _terrainSize;

			if ( u < 0f || u > 1f || v < 0f || v > 1f ) continue;

			int cX = (int)(u * (_resolution - 1f)).Clamp( 0f, _resolution - 1f );
			int cY = (int)(v * (_resolution - 1f)).Clamp( 0f, _resolution - 1f );
			int controlIndex = cX + cY * _resolution;
			uint packedMap = _controlMapPixels[controlIndex];
			var compactMat = new CompactTerrainMaterial( packedMap );

			if ( compactMat.IsHole ) continue;

			int dominantMaterialId = (compactMat.BlendFactor > 127)
				? compactMat.OverlayTextureId
				: compactMat.BaseTextureId;

			if ( dominantMaterialId < 32 )
			{
				if ( placedCountPerMaterial[dominantMaterialId] >= _materialMaxInstances[dominantMaterialId] )
					continue;
			}

			float materialTotalWeight = 0f;
			for ( int m = 0; m < modelsCount; m++ )
			{
				if ( dominantMaterialId < _modelMaterialWeights[m].Length )
				{
					materialTotalWeight += _modelMaterialWeights[m][dominantMaterialId];
				}
			}

			if ( materialTotalWeight <= 0f ) continue;

			float roll = random.NextSingle() * materialTotalWeight;
			float currentWeightSum = 0f;
			int selectedModelIndex = -1;

			for ( int m = 0; m < modelsCount; m++ )
			{
				if ( dominantMaterialId < _modelMaterialWeights[m].Length )
				{
					float w = _modelMaterialWeights[m][dominantMaterialId];
					if ( w > 0f )
					{
						currentWeightSum += w;
						if ( roll <= currentWeightSum )
						{
							selectedModelIndex = m;
							break;
						}
					}
				}
			}

			if ( selectedModelIndex == -1 ) continue;
			ref var selectedModel = ref modelEntries[selectedModelIndex];

			if ( ReduceDensityOnTransitions )
			{
				float blendNorm = compactMat.BlendFactor * 0.003921569f;
				float purity = MathF.Abs( blendNorm - 0.5f ) * 2f;
				float spawnChance = MathF.Pow( purity, TransitionThinningIntensity );
				if ( random.NextSingle() > spawnChance ) continue;
			}

			if ( selectedModel.NoiseType != ScattererNoiseType.None )
			{
				var noiseField = _modelNoiseFields[selectedModelIndex];
				float noiseVal = noiseField.Sample( worldX, worldY );
				if ( noiseVal < selectedModel.NoiseThreshold ) continue;
			}

			Vector3 normal = Vector3.Up;

			if ( UseTerrainNormal || MaxSlopeAngle < 90f )
			{
				float hL = SampleHeightBilinear( u - textureEps, v );
				float hR = SampleHeightBilinear( u + textureEps, v );
				float hD = SampleHeightBilinear( u, v - textureEps );
				float hU = SampleHeightBilinear( u, v + textureEps );
				Vector3 terrainTangentX =
					new Vector3( 2.0f * textureEps * _terrainSize, 0f, (hR - hL) * _terrainHeight );
				Vector3 terrainTangentY =
					new Vector3( 0f, 2.0f * textureEps * _terrainSize, (hU - hD) * _terrainHeight );
				Vector3 terrainNormalLocal = Vector3.Cross( terrainTangentX, terrainTangentY ).Normal;
				normal = terrainTransform.Rotation * terrainNormalLocal;
				float slopeAngle = Vector3.GetAngle( Vector3.Up, normal );
				if ( slopeAngle > MaxSlopeAngle ) continue;
			}

			float z = SampleHeightBilinear( u, v ) * _terrainHeight;
			Vector3 finalLocalPoint = new Vector3( terrainLocal.x, terrainLocal.y, z );
			Vector3 worldTerrainPoint = terrainTransform.PointToWorld( finalLocalPoint );

			Rotation randomYaw = RandomizeRotation
				? Rotation.FromYaw( random.NextSingle() * 360f )
				: Rotation.Identity;

			Rotation finalRot = UseTerrainNormal
				? Rotation.FromToRotation( Vector3.Up, normal ) * randomYaw
				: randomYaw;

			float scale = selectedModel.RandomizeScale
				? (random.NextSingle() * (selectedModel.MaxScale - selectedModel.MinScale) + selectedModel.MinScale)
				: 1.0f;

			instances.Add( new InstanceData
			{
				Position = worldTerrainPoint, Rotation = finalRot, Scale = scale, ModelIndex = selectedModelIndex
			} );

			if ( dominantMaterialId < 32 )
			{
				placedCountPerMaterial[dominantMaterialId]++;
			}
		}

		return instances;
	}

	[MethodImpl( MethodImplOptions.AggressiveInlining )]
	private int HashCombine( int seed, int value )
		=> seed ^ (value + -1640531527 + (seed << 6) + (seed >> 2));

	[MethodImpl( MethodImplOptions.AggressiveInlining )]
	private bool IsObstruction( GameObject hitObject )
	{
		if ( hitObject == null ) return false;
		if ( _cachedHitObject == hitObject ) return _cachedIsObstruction;
		_cachedHitObject = hitObject;
		_cachedIsObstruction = hitObject.Components.Get<Terrain>() == null;
		return _cachedIsObstruction;
	}

	[MethodImpl( MethodImplOptions.AggressiveInlining )]
	private float SampleHeightBilinear( float u, float v )
	{
		u = u.Clamp( 0f, 1f );
		v = v.Clamp( 0f, 1f );
		float gx = u * (_resolution - 1);
		float gy = v * (_resolution - 1);
		int ix = (int)gx;
		int iy = (int)gy;
		float fx = gx - ix;
		float fy = gy - iy;
		int ix1 = Math.Min( ix + 1, _resolution - 1 );
		int iy1 = Math.Min( iy + 1, _resolution - 1 );
		int row0 = iy * _resolution;
		int row1 = iy1 * _resolution;
		float h00 = _heightMapPixels[row0 + ix] * UshortMaxReciprocal;
		float h10 = _heightMapPixels[row0 + ix1] * UshortMaxReciprocal;
		float h01 = _heightMapPixels[row1 + ix] * UshortMaxReciprocal;
		float h11 = _heightMapPixels[row1 + ix1] * UshortMaxReciprocal;
		float h0 = h00 + (h10 - h00) * fx;
		float h1 = h01 + (h11 - h01) * fx;
		return h0 + (h1 - h0) * fy;
	}
}
kivin.performantterrainscatterer / Code/PerformantTerrainScatterer/PerformantTerrainScattererManager.cs
Game library
using System;
using System.Collections.Generic;
using Sandbox;

public sealed partial class PerformantTerrainScatterer : Component, Component.ExecuteInEditor
{
	[Property, Category( "Terrain" ), Change( nameof(InitializeSystem) )]
	public Terrain TargetTerrain { get; set; }

	[Property, Category( "Models" ), Change( nameof(InitializeSystem) )]
	public List<ScattererModelEntry> Models { get; set; } = new();

	[Property, Category( "Terrain" ), Change( nameof(InitializeSystem) ), Range( 0, 90 )]
	public float MaxSlopeAngle { get; set; } = 45f;

	[Property, Category( "Terrain" ), Change( nameof(InitializeSystem) )]
	public bool ReduceDensityOnTransitions { get; set; } = false;

	[Property, Category( "Terrain" ), Change( nameof(InitializeSystem) ),
	 ShowIf( nameof(ReduceDensityOnTransitions), true )]
	public float TransitionThinningIntensity { get; set; } = 1.0f;

	[Property, Category( "Terrain" ), Change( nameof(InitializeSystem) )]
	public bool SnapToGround { get; set; } = true;

	[Property, Category( "Collision" ), Change( nameof(InitializeSystem) )]
	public bool CheckObstructions { get; set; } = true;

	[Property, Category( "Collision" ), Change( nameof(InitializeSystem) )]
	public float TraceStartHeight { get; set; } = 200f;

	[Property, Category( "Collision" ), Change( nameof(InitializeSystem) )]
	public float GridCellSize { get; set; } = 50f;

	[Property, Category( "Collision" ), Change( nameof(InitializeSystem) )]
	public int ObstructionBuffer { get; set; } = 2;

	[Property, Category( "Collision" )]
	public string[] TraceIgnoreTags { get; set; } = { "player", "trigger", "clutter" };

	[Property, Category( "Performance" ), Change( nameof(InitializeSystem) )]
	public int MaxInstancesPerChunk { get; set; } = 500;

	[Property, Category( "Performance" ), Change( nameof(InitializeSystem) )]
	public List<MaterialInstanceOverride> MaterialOverrides { get; set; } = new();

	[Property, Category( "Performance" ), Change( nameof(InitializeSystem) )]
	public float ChunkSize { get; set; } = 500f;

	[Property, Category( "Performance" ), Change( nameof(InitializeSystem) )]
	public float ChunkLoadDistance { get; set; } = 3000f;

	[Property, Category( "Performance" )] public bool UseFrustumCulling { get; set; } = true;
	[Property, Category( "Performance" )] public float FrustumCullMinDistance { get; set; } = 800f;
	[Property, Category( "Performance" )] public bool AutomaticallyInitializeSystem { get; set; } = true;

	[Property, Category( "Terrain" ), Change( nameof(InitializeSystem) )]
	public bool UseTerrainNormal { get; set; } = true;

	[Property, Category( "Seed" ), Change( nameof(InitializeSystem) )]
	public int Seed { get; set; } = 12345;

	[Property, Category( "Randomization" ), Change( nameof(InitializeSystem) )]
	public bool RandomizeRotation { get; set; } = true;

	private readonly Dictionary<(int, int), ClutterChunk> _activeChunks = new();
	private readonly HashSet<(int, int)> _generatingChunks = new();
	private readonly List<(int, int)> _chunkRemovalList = new();

	private ushort[] _heightMapPixels;
	private uint[] _controlMapPixels;
	private int _resolution;
	private float _terrainSize;
	private float _terrainHeight;
	private float _maxRenderDistance;
	private float[][] _modelMaterialWeights;
	private GameObject _cachedHitObject;
	private bool _cachedIsObstruction;
	private Sandbox.Utility.INoiseField[] _modelNoiseFields;
	private Vector3 _lastCameraPos;
	private const float CameraUpdateThresholdSq = 10000f;
	private const float UshortMaxReciprocal = 1.0f / ushort.MaxValue;
	private SceneCustomObject _sceneObject;

	private ScattererModelEntry[] ModelEntries { get; set; }
	private float[] _modelRenderDistSq;
	private int[] _modelLodCounts;
	private float[][] _modelLodDistancesSq;

	private int[] _materialMaxInstances;
	private int _maxInstancesLoopCount;

	private ChunkRenderData[] _renderCache = Array.Empty<ChunkRenderData>();

	[ThreadStatic] private static List<Transform>[] _lodBuckets;

	protected override void OnEnabled()
	{
		if ( !TargetTerrain.IsValid() )
		{
			TargetTerrain = Scene?.GetComponentInChildren<Terrain>();
		}

		CreateSceneObject();
		if ( AutomaticallyInitializeSystem )
		{
			InitializeSystem();
		}
	}


	protected override void OnUpdate()
	{
		ManageChunks();
	}

	protected override void OnDisabled()
	{
		_sceneObject?.Delete();
		_sceneObject = null;
	}

	private void CreateSceneObject()
	{
		_sceneObject?.Delete();
		if ( Scene == null ) return;

		_sceneObject = new SceneCustomObject( Scene.SceneWorld )
		{
			RenderOverride = RenderClutter,
			Transform = new Transform( Vector3.Zero, Rotation.Identity, 1f ),
			Flags = { IsOpaque = true, IsTranslucent = false }
		};
	}

	[Button( "Reinitialize" )]
	public void InitializeSystem()
	{
		_activeChunks.Clear();
		_generatingChunks.Clear();
		_chunkRemovalList.Clear();
		_renderCache = Array.Empty<ChunkRenderData>();
		ModelEntries = null;
		_modelRenderDistSq = null;
		_modelLodCounts = null;
		_modelLodDistancesSq = null;

		if ( Models == null || Models.Count == 0 ) return;
		if ( !TargetTerrain.IsValid() || TargetTerrain.Storage == null ) return;

		// --- CONFIGURATION VALIDATION WARNINGS ---
		var seenModels = new HashSet<Model>();
		foreach ( var entry in Models )
		{
			if ( entry.Model != null && !seenModels.Add( entry.Model ) )
			{
				Log.Warning( $"[Scatterer] Duplicate model entry found for '{entry.Model.ResourceName}' in the Models configuration list." );
			}
		}

		if ( MaterialOverrides != null )
		{
			var seenOverrideMaterials = new HashSet<TerrainMaterial>();
			foreach ( var ov in MaterialOverrides )
			{
				if ( ov.Material != null && !seenOverrideMaterials.Add( ov.Material ) )
				{
					Log.Warning( $"[Scatterer] Duplicate material override configuration found for material '{ov.Material.ResourceName}'." );
				}
			}
		}

		bool hasValidModel = false;
		_maxRenderDistance = ChunkLoadDistance;

		foreach ( var t in Models )
		{
			if ( t.Model != null )
			{
				var seenMaterials = new HashSet<TerrainMaterial>();
				bool hasWeight = false;

				// MISSING NULL CHECK FIXED HERE:
				if ( t.MaterialWeights != null )
				{
					foreach ( var mw in t.MaterialWeights )
					{
						if ( mw.Material == null ) continue;

						if ( !seenMaterials.Add( mw.Material ) )
							Log.Warning(
								$"[Scatterer] Duplicate material entry '{mw.Material.ResourceName}' found for model '{t.Model.ResourceName}'." );

						if ( mw.Weight <= 0f )
							Log.Warning(
								$"[Scatterer] Weight is set to 0 for material '{mw.Material.ResourceName}' on model '{t.Model.ResourceName}'." );

						if ( mw.Weight > 0f ) hasWeight = true;
					}
				}

				if ( hasWeight ) hasValidModel = true;

				if ( t.RenderDistance > _maxRenderDistance )
					_maxRenderDistance = t.RenderDistance;
			}
		}

		if ( !hasValidModel ) return;

		ModelEntries = Models.ToArray();
		_modelRenderDistSq = new float[ModelEntries.Length];
		_modelLodCounts = new int[ModelEntries.Length];
		_modelLodDistancesSq = new float[ModelEntries.Length][];

		for ( int m = 0; m < ModelEntries.Length; m++ )
		{
			_modelRenderDistSq[m] = ModelEntries[m].RenderDistance * ModelEntries[m].RenderDistance;
			var mdl = ModelEntries[m].Model;

			if ( mdl != null )
			{
				_modelLodCounts[m] = Math.Max( 1, mdl.MeshInfo.LodCount );
				if ( mdl.MeshInfo.LodSwitchDistances != null && mdl.MeshInfo.LodSwitchDistances.Length > 0 &&
				     mdl.MeshInfo.LodSwitchDistances[0] > 0f )
				{
					var switchDistances = mdl.MeshInfo.LodSwitchDistances;
					_modelLodDistancesSq[m] = new float[switchDistances.Length];
					for ( int i = 0; i < switchDistances.Length; i++ )
					{
						float dist = switchDistances[i] * ModelEntries[m].LodBias;
						_modelLodDistancesSq[m][i] = dist * dist;
					}
				}
				else if ( _modelLodCounts[m] > 1 )
				{
					int switchCount = _modelLodCounts[m] - 1;
					_modelLodDistancesSq[m] = new float[switchCount];
					float step = ModelEntries[m].RenderDistance / _modelLodCounts[m];
					for ( int i = 0; i < switchCount; i++ )
					{
						float dist = (step * (i + 1)) * ModelEntries[m].LodBias;
						_modelLodDistancesSq[m][i] = dist * dist;
					}
				}
				else
				{
					_modelLodDistancesSq[m] = Array.Empty<float>();
				}
			}
			else
			{
				_modelLodCounts[m] = 1;
				_modelLodDistancesSq[m] = Array.Empty<float>();
			}
		}

		_heightMapPixels = TargetTerrain.Storage.HeightMap;
		_controlMapPixels = TargetTerrain.Storage.ControlMap;
		_resolution = TargetTerrain.Storage.Resolution;
		_terrainSize = TargetTerrain.TerrainSize;
		_terrainHeight = TargetTerrain.TerrainHeight;

		_modelMaterialWeights = new float[Models.Count][];

		for ( int m = 0; m < Models.Count; m++ )
		{
			_modelMaterialWeights[m] = new float[32];
			if ( TargetTerrain.Storage.Materials != null && Models[m].MaterialWeights != null )
			{
				for ( int i = 0; i < TargetTerrain.Storage.Materials.Count; i++ )
				{
					foreach ( var mw in Models[m].MaterialWeights )
					{
						if ( mw.Material == TargetTerrain.Storage.Materials[i] )
						{
							_modelMaterialWeights[m][i] = mw.Weight;
							break;
						}
					}
				}
			}
		}

		_modelNoiseFields = new Sandbox.Utility.INoiseField[Models.Count];
		for ( int m = 0; m < Models.Count; m++ )
		{
			var modelEntry = Models[m];
			if ( modelEntry.NoiseType == ScattererNoiseType.None ) continue;
			var parameters = new Sandbox.Utility.Noise.Parameters( Seed + m, modelEntry.NoiseScale );

			if ( modelEntry.NoiseType == ScattererNoiseType.Perlin )
				_modelNoiseFields[m] = Sandbox.Utility.Noise.PerlinField( parameters );
			else if ( modelEntry.NoiseType == ScattererNoiseType.Simplex )
				_modelNoiseFields[m] = Sandbox.Utility.Noise.SimplexField( parameters );
		}

		// --- MAPPING AND CEILING INITIALIZATION ---
		_materialMaxInstances = new int[32];
		Array.Fill( _materialMaxInstances, MaxInstancesPerChunk );
		_maxInstancesLoopCount = MaxInstancesPerChunk;

		if ( TargetTerrain.Storage.Materials != null && MaterialOverrides != null )
		{
			for ( int i = 0; i < TargetTerrain.Storage.Materials.Count; i++ )
			{
				foreach ( var ov in MaterialOverrides )
				{
					if ( ov.Material == TargetTerrain.Storage.Materials[i] )
					{
						_materialMaxInstances[i] = ov.MaxInstances;
						if ( ov.MaxInstances > _maxInstancesLoopCount )
						{
							_maxInstancesLoopCount = ov.MaxInstances;
						}
						break;
					}
				}
			}
		}

		RebuildRenderCache();
	}
}
kivin.performantterrainscatterer / UnitTests/LibraryTest.cs
UnitTest library
using Sandbox;

[TestClass]
public partial class LibraryTests
{
	[TestMethod]
	public void SceneTest()
	{
		var scene = new Scene();
		using ( scene.Push() )
		{
			var go = new GameObject();

			Assert.AreEqual( 1, scene.Directory.GameObjectCount );
		}
	}

}
kivin.performantterrainscatterer / Code/PerformantTerrainScatterer/PerformantTerrainScattererManager.Chunks.cs
Game library
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Threading.Tasks;
using Sandbox;

public sealed partial class PerformantTerrainScatterer
{
	private void ManageChunks()
	{
		if ( Scene == null ) return;
		var camera = Scene.Camera;
		if ( !camera.IsValid() ) return;
		Vector3 cameraPos = camera.WorldPosition;

		if ( _activeChunks.Count > 0 && cameraPos.DistanceSquared( _lastCameraPos ) < CameraUpdateThresholdSq )
			return;

		_lastCameraPos = cameraPos;
		int cameraCx = (int)MathF.Floor( cameraPos.x / ChunkSize );
		int cameraCy = (int)MathF.Floor( cameraPos.y / ChunkSize );
		int chunksInRadius = (int)MathF.Ceiling( ChunkLoadDistance / ChunkSize );

		for ( int dx = -chunksInRadius; dx <= chunksInRadius; dx++ )
		{
			for ( int dy = -chunksInRadius; dy <= chunksInRadius; dy++ )
			{
				var chunkCoord = (cameraCx + dx, cameraCy + dy);
				if ( !_activeChunks.ContainsKey( chunkCoord ) && _generatingChunks.Add( chunkCoord ) )
					_ = GenerateChunkAsync( chunkCoord.Item1, chunkCoord.Item2 );
			}
		}

		_chunkRemovalList.Clear();
		foreach ( var key in _activeChunks.Keys )
		{
			if ( Math.Abs( key.Item1 - cameraCx ) > chunksInRadius ||
			     Math.Abs( key.Item2 - cameraCy ) > chunksInRadius )
				_chunkRemovalList.Add( key );
		}

		foreach ( var t in _chunkRemovalList )
			_activeChunks.Remove( t );

		if ( _chunkRemovalList.Count > 0 )
			RebuildRenderCache();
	}

	private async Task GenerateChunkAsync( int cx, int cy )
	{
		try
		{
			if ( !IsValid || Scene == null ) return;
			Transform terrainTransform = TargetTerrain.IsValid() ? TargetTerrain.WorldTransform : new Transform();

			var tentativeInstances =
				await GameTask.RunInThreadAsync( () => CalculateChunkPlacements( cx, cy, terrainTransform ) );

			if ( !IsValid || Scene == null ) return;

			var modelEntries = ModelEntries;
			if ( tentativeInstances == null || modelEntries == null ) return;

			int cellsPerChunk = (int)MathF.Ceiling( ChunkSize / GridCellSize );
			int gridPadding = ObstructionBuffer;
			int gridWidth = cellsPerChunk + (gridPadding * 2) + 2;
			int gridArea = gridWidth * gridWidth;

			bool[] localBlockedGrid = ArrayPool<bool>.Shared.Rent( gridArea );
			Array.Clear( localBlockedGrid, 0, gridArea );

			try
			{
				int minGridX = (int)MathF.Floor( (cx * ChunkSize) / GridCellSize ) - gridPadding;
				int minGridY = (int)MathF.Floor( (cy * ChunkSize) / GridCellSize ) - gridPadding;
				int traceCounter = 0;
				bool needsTrace = CheckObstructions || SnapToGround;
				List<Transform>[] modelLists = new List<Transform>[modelEntries.Length];
				float cxSum = 0f, cySum = 0f, czSum = 0f;
				int placedCount = 0;
				Vector3 traceUpOffset = Vector3.Up * TraceStartHeight;

				foreach ( var inst in tentativeInstances )
				{
					if ( !IsValid || Scene == null ) return;

					Vector3 finalPosition = inst.Position;
					int gridX = (int)MathF.Floor( inst.Position.x / GridCellSize );
					int gridY = (int)MathF.Floor( inst.Position.y / GridCellSize );
					int localX = gridX - minGridX;
					int localY = gridY - minGridY;

					if ( CheckObstructions )
					{
						bool isBlocked = false;
						for ( int dx = -ObstructionBuffer; dx <= ObstructionBuffer; dx++ )
						{
							int nx = localX + dx;
							if ( nx < 0 || nx >= gridWidth ) continue;
							for ( int dy = -ObstructionBuffer; dy <= ObstructionBuffer; dy++ )
							{
								int ny = localY + dy;
								if ( ny < 0 || ny >= gridWidth ) continue;
								if ( localBlockedGrid[nx + ny * gridWidth] )
								{
									isBlocked = true;
									break;
								}
							}

							if ( isBlocked ) break;
						}

						if ( isBlocked ) continue;
					}

					if ( needsTrace )
					{
						var traceStart = inst.Position + traceUpOffset;
						var traceEnd = inst.Position - traceUpOffset;
						var trace = Scene.Trace.Ray( traceStart, traceEnd )
							.WithoutTags( TraceIgnoreTags )
							.Run();

						bool hitObstruction = trace.Hit && IsObstruction( trace.GameObject );

						if ( CheckObstructions && (trace.StartedSolid || hitObstruction) )
						{
							if ( localX >= 0 && localX < gridWidth && localY >= 0 && localY < gridWidth )
								localBlockedGrid[localX + localY * gridWidth] = true;
							continue;
						}

						if ( SnapToGround && trace.Hit && !trace.StartedSolid && !hitObstruction )
							finalPosition = trace.HitPosition;

						traceCounter++;
						if ( traceCounter % 100 == 0 )
						{
							await GameTask.Yield();
							if ( !IsValid || Scene == null ) return;
						}
					}

					if ( inst.ModelIndex < 0 || inst.ModelIndex >= modelEntries.Length ) continue;

					finalPosition += Vector3.Up * modelEntries[inst.ModelIndex].ZOffset;
					var list = modelLists[inst.ModelIndex] ??= new List<Transform>();
					list.Add( new Transform( finalPosition, inst.Rotation, inst.Scale ) );

					cxSum += finalPosition.x;
					cySum += finalPosition.y;
					czSum += finalPosition.z;
					placedCount++;
				}

				if ( !IsValid ) return;

				var chunk = new ClutterChunk();
				chunk.Center = placedCount > 0
					? new Vector3( cxSum / placedCount, cySum / placedCount, czSum / placedCount )
					: new Vector3( cx * ChunkSize + ChunkSize * 0.5f, cy * ChunkSize + ChunkSize * 0.5f, 0f );

				for ( int i = 0; i < modelLists.Length; i++ )
				{
					if ( modelLists[i] != null && modelLists[i].Count > 0 )
						chunk.TransformsByModel[i] = modelLists[i].ToArray();
				}

				_activeChunks[(cx, cy)] = chunk;
				RebuildRenderCache();
			}
			finally
			{
				ArrayPool<bool>.Shared.Return( localBlockedGrid );
			}
		}
		catch ( Exception )
		{
		}
		finally
		{
			_generatingChunks.Remove( (cx, cy) );
		}
	}
}
kivin.performantterrainscatterer / Code/PerformantTerrainScatterer/PerformantTerrainScattererManager.Rendering.cs
Game library
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Sandbox;

public sealed partial class PerformantTerrainScatterer
{
	private void RebuildRenderCache()
	{
		var newCache = new ChunkRenderData[_activeChunks.Count];
		int index = 0;

		foreach ( var chunk in _activeChunks.Values )
		{
			var batches = new ModelRenderBatch[chunk.TransformsByModel.Count];
			int bIndex = 0;

			foreach ( var kvp in chunk.TransformsByModel )
			{
				int mIdx = kvp.Key;
				batches[bIndex++] = new ModelRenderBatch
				{
					Model = ModelEntries[mIdx].Model,
					Transforms = kvp.Value,
					RenderDistSq = _modelRenderDistSq[mIdx],
					LodDistancesSq = _modelLodDistancesSq[mIdx],
					MaxLod = Math.Max( 0, _modelLodCounts[mIdx] - 1 )
				};
			}

			newCache[index++] = new ChunkRenderData { Center = chunk.Center, Batches = batches };
		}

		_renderCache = newCache;
	}

	private void RenderClutter( SceneObject obj )
	{
		var cache = _renderCache;
		if ( cache.Length == 0 ) return;

		if ( Scene == null ) return;
		var camera = Scene.Camera;
		if ( !camera.IsValid() ) return;

		Vector3 cameraPos = camera.WorldPosition;
		Vector3 cameraForward = camera.WorldRotation.Forward;

		float frustumMinDistSq = FrustumCullMinDistance * FrustumCullMinDistance;
		float maxRenderDistSq = _maxRenderDistance * _maxRenderDistance;
		bool useFrustum = UseFrustumCulling;

		if ( _lodBuckets == null )
		{
			_lodBuckets = new List<Transform>[16];
			for ( int i = 0; i < _lodBuckets.Length; i++ )
				_lodBuckets[i] = new List<Transform>();
		}

		for ( int c = 0; c < cache.Length; c++ )
		{
			ref var chunk = ref cache[c];

			Vector3 toChunk = chunk.Center - cameraPos;
			float chunkDistSq = toChunk.LengthSquared;

			if ( chunkDistSq > maxRenderDistSq ) continue;

			if ( useFrustum && chunkDistSq > frustumMinDistSq )
			{
				float dot = Vector3.Dot( cameraForward, toChunk );
				if ( dot < 0f && (dot * dot) > 0.04f * chunkDistSq )
					continue;
			}

			var batches = chunk.Batches;
			for ( int b = 0; b < batches.Length; b++ )
			{
				ref var batch = ref batches[b];
				if ( chunkDistSq > batch.RenderDistSq ) continue;

				int maxLod = batch.MaxLod;

				for ( int i = 0; i <= maxLod; i++ )
					_lodBuckets[i].Clear();

				var transforms = batch.Transforms;
				var lodDists = batch.LodDistancesSq;

				for ( int i = 0; i < transforms.Length; i++ )
				{
					Vector3 instPos = transforms[i].Position;
					float instDistSq = (instPos - cameraPos).LengthSquared;

					if ( instDistSq > batch.RenderDistSq ) continue;

					int lod = 0;
					for ( int l = 0; l < lodDists.Length; l++ )
					{
						if ( instDistSq > lodDists[l] )
							lod = l + 1;
						else
							break;
					}

					if ( lod > maxLod ) lod = maxLod;

					_lodBuckets[lod].Add( transforms[i] );
				}

				for ( int l = 0; l <= maxLod; l++ )
				{
					var bucket = _lodBuckets[l];
					if ( bucket.Count > 0 )
					{
						Graphics.DrawModelInstanced( batch.Model, CollectionsMarshal.AsSpan( bucket ), l );
					}
				}
			}
		}
	}
}
kivin.performantterrainscatterer / Code/PerformantTerrainScatterer/PerformantTerrainScattererManager.Placement.cs
Game library
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Sandbox;

public sealed partial class PerformantTerrainScatterer
{
	private List<InstanceData> CalculateChunkPlacements( int cx, int cy, Transform terrainTransform )
	{
		if ( _heightMapPixels == null || _controlMapPixels == null ) return null;
		var modelEntries = ModelEntries;
		if ( modelEntries == null || modelEntries.Length == 0 ) return null;

		int loopCount = _maxInstancesLoopCount;
		var instances = new List<InstanceData>( loopCount );
		int chunkSeed = HashCombine( Seed, HashCombine( cx, cy ) );
		var random = new Random( chunkSeed );
		float textureEps = 1.0f / (_resolution - 1);
		float chunkBaseX = cx * ChunkSize;
		float chunkBaseY = cy * ChunkSize;
		int modelsCount = modelEntries.Length;

		int[] placedCountPerMaterial = new int[32];

		for ( int i = 0; i < loopCount; i++ )
		{
			float localX = random.NextSingle() * ChunkSize;
			float localY = random.NextSingle() * ChunkSize;
			float worldX = chunkBaseX + localX;
			float worldY = chunkBaseY + localY;

			Vector3 worldAbsPoint = new Vector3( worldX, worldY, 0f );
			Vector3 terrainLocal = terrainTransform.PointToLocal( worldAbsPoint );
			float u = terrainLocal.x / _terrainSize;
			float v = terrainLocal.y / _terrainSize;

			if ( u < 0f || u > 1f || v < 0f || v > 1f ) continue;

			int cX = (int)(u * (_resolution - 1f)).Clamp( 0f, _resolution - 1f );
			int cY = (int)(v * (_resolution - 1f)).Clamp( 0f, _resolution - 1f );
			int controlIndex = cX + cY * _resolution;
			uint packedMap = _controlMapPixels[controlIndex];
			var compactMat = new CompactTerrainMaterial( packedMap );

			if ( compactMat.IsHole ) continue;

			int dominantMaterialId = (compactMat.BlendFactor > 127)
				? compactMat.OverlayTextureId
				: compactMat.BaseTextureId;

			if ( dominantMaterialId < 32 )
			{
				if ( placedCountPerMaterial[dominantMaterialId] >= _materialMaxInstances[dominantMaterialId] )
					continue;
			}

			float materialTotalWeight = 0f;
			for ( int m = 0; m < modelsCount; m++ )
			{
				if ( dominantMaterialId < _modelMaterialWeights[m].Length )
				{
					materialTotalWeight += _modelMaterialWeights[m][dominantMaterialId];
				}
			}

			if ( materialTotalWeight <= 0f ) continue;

			float roll = random.NextSingle() * materialTotalWeight;
			float currentWeightSum = 0f;
			int selectedModelIndex = -1;

			for ( int m = 0; m < modelsCount; m++ )
			{
				if ( dominantMaterialId < _modelMaterialWeights[m].Length )
				{
					float w = _modelMaterialWeights[m][dominantMaterialId];
					if ( w > 0f )
					{
						currentWeightSum += w;
						if ( roll <= currentWeightSum )
						{
							selectedModelIndex = m;
							break;
						}
					}
				}
			}

			if ( selectedModelIndex == -1 ) continue;
			ref var selectedModel = ref modelEntries[selectedModelIndex];

			if ( ReduceDensityOnTransitions )
			{
				float blendNorm = compactMat.BlendFactor * 0.003921569f;
				float purity = MathF.Abs( blendNorm - 0.5f ) * 2f;
				float spawnChance = MathF.Pow( purity, TransitionThinningIntensity );
				if ( random.NextSingle() > spawnChance ) continue;
			}

			if ( selectedModel.NoiseType != ScattererNoiseType.None )
			{
				var noiseField = _modelNoiseFields[selectedModelIndex];
				float noiseVal = noiseField.Sample( worldX, worldY );
				if ( noiseVal < selectedModel.NoiseThreshold ) continue;
			}

			Vector3 normal = Vector3.Up;

			if ( UseTerrainNormal || MaxSlopeAngle < 90f )
			{
				float hL = SampleHeightBilinear( u - textureEps, v );
				float hR = SampleHeightBilinear( u + textureEps, v );
				float hD = SampleHeightBilinear( u, v - textureEps );
				float hU = SampleHeightBilinear( u, v + textureEps );
				Vector3 terrainTangentX =
					new Vector3( 2.0f * textureEps * _terrainSize, 0f, (hR - hL) * _terrainHeight );
				Vector3 terrainTangentY =
					new Vector3( 0f, 2.0f * textureEps * _terrainSize, (hU - hD) * _terrainHeight );
				Vector3 terrainNormalLocal = Vector3.Cross( terrainTangentX, terrainTangentY ).Normal;
				normal = terrainTransform.Rotation * terrainNormalLocal;
				float slopeAngle = Vector3.GetAngle( Vector3.Up, normal );
				if ( slopeAngle > MaxSlopeAngle ) continue;
			}

			float z = SampleHeightBilinear( u, v ) * _terrainHeight;
			Vector3 finalLocalPoint = new Vector3( terrainLocal.x, terrainLocal.y, z );
			Vector3 worldTerrainPoint = terrainTransform.PointToWorld( finalLocalPoint );

			Rotation randomYaw = RandomizeRotation
				? Rotation.FromYaw( random.NextSingle() * 360f )
				: Rotation.Identity;

			Rotation finalRot = UseTerrainNormal
				? Rotation.FromToRotation( Vector3.Up, normal ) * randomYaw
				: randomYaw;

			float scale = selectedModel.RandomizeScale
				? (random.NextSingle() * (selectedModel.MaxScale - selectedModel.MinScale) + selectedModel.MinScale)
				: 1.0f;

			instances.Add( new InstanceData
			{
				Position = worldTerrainPoint, Rotation = finalRot, Scale = scale, ModelIndex = selectedModelIndex
			} );

			if ( dominantMaterialId < 32 )
			{
				placedCountPerMaterial[dominantMaterialId]++;
			}
		}

		return instances;
	}

	[MethodImpl( MethodImplOptions.AggressiveInlining )]
	private int HashCombine( int seed, int value )
		=> seed ^ (value + -1640531527 + (seed << 6) + (seed >> 2));

	[MethodImpl( MethodImplOptions.AggressiveInlining )]
	private bool IsObstruction( GameObject hitObject )
	{
		if ( hitObject == null ) return false;
		if ( _cachedHitObject == hitObject ) return _cachedIsObstruction;
		_cachedHitObject = hitObject;
		_cachedIsObstruction = hitObject.Components.Get<Terrain>() == null;
		return _cachedIsObstruction;
	}

	[MethodImpl( MethodImplOptions.AggressiveInlining )]
	private float SampleHeightBilinear( float u, float v )
	{
		u = u.Clamp( 0f, 1f );
		v = v.Clamp( 0f, 1f );
		float gx = u * (_resolution - 1);
		float gy = v * (_resolution - 1);
		int ix = (int)gx;
		int iy = (int)gy;
		float fx = gx - ix;
		float fy = gy - iy;
		int ix1 = Math.Min( ix + 1, _resolution - 1 );
		int iy1 = Math.Min( iy + 1, _resolution - 1 );
		int row0 = iy * _resolution;
		int row1 = iy1 * _resolution;
		float h00 = _heightMapPixels[row0 + ix] * UshortMaxReciprocal;
		float h10 = _heightMapPixels[row0 + ix1] * UshortMaxReciprocal;
		float h01 = _heightMapPixels[row1 + ix] * UshortMaxReciprocal;
		float h11 = _heightMapPixels[row1 + ix1] * UshortMaxReciprocal;
		float h0 = h00 + (h10 - h00) * fx;
		float h1 = h01 + (h11 - h01) * fx;
		return h0 + (h1 - h0) * fy;
	}
}
Debug: View Raw JSON Response
{
    "TotalCount": 14,
    "Files": [
        {
            "Ident": "kivin.performantterrainscatterer",
            "Path": ".obj/__compiler_extra.cs",
            "FileName": "__compiler_extra.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 293458,
            "IsPrivate": false,
            "Code": "global using static Sandbox.Internal.GlobalGameNamespace;\r\nglobal using Microsoft.AspNetCore.Components;\r\nglobal using Microsoft.AspNetCore.Components.Rendering;\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"AddonTitle\", \"Performant Terrain Scatterer\" )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"AddonIdent\", \"performantterrainscatterer\" )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"OrgIdent\", \"kivin\" )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"Ident\", \"kivin.performantterrainscatterer\" )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"EngineVersion\", \"26\" )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"EngineMinorVersion\", \"1\" )]\r\n\r\n[assembly: System.Runtime.Versioning.TargetFramework( \".NETCoreApp,Version=v9.0\", FrameworkDisplayName = \".NET 9.0\" )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"CompileTime\", \"2026-06-23T11:51:31.4189525Z\" )]\r\n[assembly: global::System.Reflection.AssemblyVersion(\"0.0.119.0\")]\r\n[assembly: global::System.Reflection.AssemblyFileVersion(\"0.0.119.0\")]"
        },
        {
            "Ident": "kivin.performantterrainscatterer",
            "Path": "PerformantTerrainScatterer/PerformantTerrainScattererManager.Chunks.cs",
            "FileName": "PerformantTerrainScattererManager.Chunks.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 293458,
            "IsPrivate": false,
            "Code": "using System;\r\nusing System.Buffers;\r\nusing System.Collections.Generic;\r\nusing System.Threading.Tasks;\r\nusing Sandbox;\r\n\r\npublic sealed partial class PerformantTerrainScatterer\r\n{\r\n\tprivate void ManageChunks()\r\n\t{\r\n\t\tif ( Scene == null ) return;\r\n\t\tvar camera = Scene.Camera;\r\n\t\tif ( !camera.IsValid() ) return;\r\n\t\tVector3 cameraPos = camera.WorldPosition;\r\n\r\n\t\tif ( _activeChunks.Count > 0 && cameraPos.DistanceSquared( _lastCameraPos ) < CameraUpdateThresholdSq )\r\n\t\t\treturn;\r\n\r\n\t\t_lastCameraPos = cameraPos;\r\n\t\tint cameraCx = (int)MathF.Floor( cameraPos.x / ChunkSize );\r\n\t\tint cameraCy = (int)MathF.Floor( cameraPos.y / ChunkSize );\r\n\t\tint chunksInRadius = (int)MathF.Ceiling( ChunkLoadDistance / ChunkSize );\r\n\r\n\t\tfor ( int dx = -chunksInRadius; dx <= chunksInRadius; dx++ )\r\n\t\t{\r\n\t\t\tfor ( int dy = -chunksInRadius; dy <= chunksInRadius; dy++ )\r\n\t\t\t{\r\n\t\t\t\tvar chunkCoord = (cameraCx + dx, cameraCy + dy);\r\n\t\t\t\tif ( !_activeChunks.ContainsKey( chunkCoord ) && _generatingChunks.Add( chunkCoord ) )\r\n\t\t\t\t\t_ = GenerateChunkAsync( chunkCoord.Item1, chunkCoord.Item2 );\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t_chunkRemovalList.Clear();\r\n\t\tforeach ( var key in _activeChunks.Keys )\r\n\t\t{\r\n\t\t\tif ( Math.Abs( key.Item1 - cameraCx ) > chunksInRadius ||\r\n\t\t\t     Math.Abs( key.Item2 - cameraCy ) > chunksInRadius )\r\n\t\t\t\t_chunkRemovalList.Add( key );\r\n\t\t}\r\n\r\n\t\tforeach ( var t in _chunkRemovalList )\r\n\t\t\t_activeChunks.Remove( t );\r\n\r\n\t\tif ( _chunkRemovalList.Count > 0 )\r\n\t\t\tRebuildRenderCache();\r\n\t}\r\n\r\n\tprivate async Task GenerateChunkAsync( int cx, int cy )\r\n\t{\r\n\t\ttry\r\n\t\t{\r\n\t\t\tif ( !IsValid || Scene == null ) return;\r\n\t\t\tTransform terrainTransform = TargetTerrain.IsValid() ? TargetTerrain.WorldTransform : new Transform();\r\n\r\n\t\t\tvar tentativeInstances =\r\n\t\t\t\tawait GameTask.RunInThreadAsync( () => CalculateChunkPlacements( cx, cy, terrainTransform ) );\r\n\r\n\t\t\tif ( !IsValid || Scene == null ) return;\r\n\r\n\t\t\tvar modelEntries = ModelEntries;\r\n\t\t\tif ( tentativeInstances == null || modelEntries == null ) return;\r\n\r\n\t\t\tint cellsPerChunk = (int)MathF.Ceiling( ChunkSize / GridCellSize );\r\n\t\t\tint gridPadding = ObstructionBuffer;\r\n\t\t\tint gridWidth = cellsPerChunk + (gridPadding * 2) + 2;\r\n\t\t\tint gridArea = gridWidth * gridWidth;\r\n\r\n\t\t\tbool[] localBlockedGrid = ArrayPool<bool>.Shared.Rent( gridArea );\r\n\t\t\tArray.Clear( localBlockedGrid, 0, gridArea );\r\n\r\n\t\t\ttry\r\n\t\t\t{\r\n\t\t\t\tint minGridX = (int)MathF.Floor( (cx * ChunkSize) / GridCellSize ) - gridPadding;\r\n\t\t\t\tint minGridY = (int)MathF.Floor( (cy * ChunkSize) / GridCellSize ) - gridPadding;\r\n\t\t\t\tint traceCounter = 0;\r\n\t\t\t\tbool needsTrace = CheckObstructions || SnapToGround;\r\n\t\t\t\tList<Transform>[] modelLists = new List<Transform>[modelEntries.Length];\r\n\t\t\t\tfloat cxSum = 0f, cySum = 0f, czSum = 0f;\r\n\t\t\t\tint placedCount = 0;\r\n\t\t\t\tVector3 traceUpOffset = Vector3.Up * TraceStartHeight;\r\n\r\n\t\t\t\tforeach ( var inst in tentativeInstances )\r\n\t\t\t\t{\r\n\t\t\t\t\tif ( !IsValid || Scene == null ) return;\r\n\r\n\t\t\t\t\tVector3 finalPosition = inst.Position;\r\n\t\t\t\t\tint gridX = (int)MathF.Floor( inst.Position.x / GridCellSize );\r\n\t\t\t\t\tint gridY = (int)MathF.Floor( inst.Position.y / GridCellSize );\r\n\t\t\t\t\tint localX = gridX - minGridX;\r\n\t\t\t\t\tint localY = gridY - minGridY;\r\n\r\n\t\t\t\t\tif ( CheckObstructions )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tbool isBlocked = false;\r\n\t\t\t\t\t\tfor ( int dx = -ObstructionBuffer; dx <= ObstructionBuffer; dx++ )\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tint nx = localX + dx;\r\n\t\t\t\t\t\t\tif ( nx < 0 || nx >= gridWidth ) continue;\r\n\t\t\t\t\t\t\tfor ( int dy = -ObstructionBuffer; dy <= ObstructionBuffer; dy++ )\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\tint ny = localY + dy;\r\n\t\t\t\t\t\t\t\tif ( ny < 0 || ny >= gridWidth ) continue;\r\n\t\t\t\t\t\t\t\tif ( localBlockedGrid[nx + ny * gridWidth] )\r\n\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\tisBlocked = true;\r\n\t\t\t\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\t\tif ( isBlocked ) break;\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\tif ( isBlocked ) continue;\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tif ( needsTrace )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar traceStart = inst.Position + traceUpOffset;\r\n\t\t\t\t\t\tvar traceEnd = inst.Position - traceUpOffset;\r\n\t\t\t\t\t\tvar trace = Scene.Trace.Ray( traceStart, traceEnd )\r\n\t\t\t\t\t\t\t.WithoutTags( TraceIgnoreTags )\r\n\t\t\t\t\t\t\t.Run();\r\n\r\n\t\t\t\t\t\tbool hitObstruction = trace.Hit && IsObstruction( trace.GameObject );\r\n\r\n\t\t\t\t\t\tif ( CheckObstructions && (trace.StartedSolid || hitObstruction) )\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tif ( localX >= 0 && localX < gridWidth && localY >= 0 && localY < gridWidth )\r\n\t\t\t\t\t\t\t\tlocalBlockedGrid[localX + localY * gridWidth] = true;\r\n\t\t\t\t\t\t\tcontinue;\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\tif ( SnapToGround && trace.Hit && !trace.StartedSolid && !hitObstruction )\r\n\t\t\t\t\t\t\tfinalPosition = trace.HitPosition;\r\n\r\n\t\t\t\t\t\ttraceCounter++;\r\n\t\t\t\t\t\tif ( traceCounter % 100 == 0 )\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tawait GameTask.Yield();\r\n\t\t\t\t\t\t\tif ( !IsValid || Scene == null ) return;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tif ( inst.ModelIndex < 0 || inst.ModelIndex >= modelEntries.Length ) continue;\r\n\r\n\t\t\t\t\tfinalPosition += Vector3.Up * modelEntries[inst.ModelIndex].ZOffset;\r\n\t\t\t\t\tvar list = modelLists[inst.ModelIndex] ??= new List<Transform>();\r\n\t\t\t\t\tlist.Add( new Transform( finalPosition, inst.Rotation, inst.Scale ) );\r\n\r\n\t\t\t\t\tcxSum += finalPosition.x;\r\n\t\t\t\t\tcySum += finalPosition.y;\r\n\t\t\t\t\tczSum += finalPosition.z;\r\n\t\t\t\t\tplacedCount++;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif ( !IsValid ) return;\r\n\r\n\t\t\t\tvar chunk = new ClutterChunk();\r\n\t\t\t\tchunk.Center = placedCount > 0\r\n\t\t\t\t\t? new Vector3( cxSum / placedCount, cySum / placedCount, czSum / placedCount )\r\n\t\t\t\t\t: new Vector3( cx * ChunkSize + ChunkSize * 0.5f, cy * ChunkSize + ChunkSize * 0.5f, 0f );\r\n\r\n\t\t\t\tfor ( int i = 0; i < modelLists.Length; i++ )\r\n\t\t\t\t{\r\n\t\t\t\t\tif ( modelLists[i] != null && modelLists[i].Count > 0 )\r\n\t\t\t\t\t\tchunk.TransformsByModel[i] = modelLists[i].ToArray();\r\n\t\t\t\t}\r\n\r\n\t\t\t\t_activeChunks[(cx, cy)] = chunk;\r\n\t\t\t\tRebuildRenderCache();\r\n\t\t\t}\r\n\t\t\tfinally\r\n\t\t\t{\r\n\t\t\t\tArrayPool<bool>.Shared.Return( localBlockedGrid );\r\n\t\t\t}\r\n\t\t}\r\n\t\tcatch ( Exception )\r\n\t\t{\r\n\t\t}\r\n\t\tfinally\r\n\t\t{\r\n\t\t\t_generatingChunks.Remove( (cx, cy) );\r\n\t\t}\r\n\t}\r\n}\r\n"
        },
        {
            "Ident": "kivin.performantterrainscatterer",
            "Path": "UnitTests/UnitTest.cs",
            "FileName": "UnitTest.cs",
            "PackageType": "library",
            "CodeKind": "UnitTest",
            "AssetVersionId": 293458,
            "IsPrivate": false,
            "Code": "global using Microsoft.VisualStudio.TestTools.UnitTesting;\r\n\r\n[TestClass]\r\npublic class TestInit\r\n{\r\n\tpublic static Sandbox.TestAppSystem AppSystem;\r\n\r\n\t[AssemblyInitialize]\r\n\tpublic static void AssemblyInitialize( TestContext context )\r\n\t{\r\n\t\tAppSystem = new Sandbox.TestAppSystem();\r\n\t\tAppSystem.Init();\r\n\t}\r\n\r\n\t[AssemblyCleanup]\r\n\tpublic static void AssemblyCleanup()\r\n\t{\r\n\t\tAppSystem.Shutdown();\r\n\t}\r\n}\r\n"
        },
        {
            "Ident": "kivin.performantterrainscatterer",
            "Path": "PerformantTerrainScatterer/PerformantTerrainScattererManager.cs",
            "FileName": "PerformantTerrainScattererManager.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 293458,
            "IsPrivate": false,
            "Code": "using System;\r\nusing System.Collections.Generic;\r\nusing Sandbox;\r\n\r\npublic sealed partial class PerformantTerrainScatterer : Component, Component.ExecuteInEditor\r\n{\r\n\t[Property, Category( \"Terrain\" ), Change( nameof(InitializeSystem) )]\r\n\tpublic Terrain TargetTerrain { get; set; }\r\n\r\n\t[Property, Category( \"Models\" ), Change( nameof(InitializeSystem) )]\r\n\tpublic List<ScattererModelEntry> Models { get; set; } = new();\r\n\r\n\t[Property, Category( \"Terrain\" ), Change( nameof(InitializeSystem) ), Range( 0, 90 )]\r\n\tpublic float MaxSlopeAngle { get; set; } = 45f;\r\n\r\n\t[Property, Category( \"Terrain\" ), Change( nameof(InitializeSystem) )]\r\n\tpublic bool ReduceDensityOnTransitions { get; set; } = false;\r\n\r\n\t[Property, Category( \"Terrain\" ), Change( nameof(InitializeSystem) ),\r\n\t ShowIf( nameof(ReduceDensityOnTransitions), true )]\r\n\tpublic float TransitionThinningIntensity { get; set; } = 1.0f;\r\n\r\n\t[Property, Category( \"Terrain\" ), Change( nameof(InitializeSystem) )]\r\n\tpublic bool SnapToGround { get; set; } = true;\r\n\r\n\t[Property, Category( \"Collision\" ), Change( nameof(InitializeSystem) )]\r\n\tpublic bool CheckObstructions { get; set; } = true;\r\n\r\n\t[Property, Category( \"Collision\" ), Change( nameof(InitializeSystem) )]\r\n\tpublic float TraceStartHeight { get; set; } = 200f;\r\n\r\n\t[Property, Category( \"Collision\" ), Change( nameof(InitializeSystem) )]\r\n\tpublic float GridCellSize { get; set; } = 50f;\r\n\r\n\t[Property, Category( \"Collision\" ), Change( nameof(InitializeSystem) )]\r\n\tpublic int ObstructionBuffer { get; set; } = 2;\r\n\r\n\t[Property, Category( \"Collision\" )]\r\n\tpublic string[] TraceIgnoreTags { get; set; } = { \"player\", \"trigger\", \"clutter\" };\r\n\r\n\t[Property, Category( \"Performance\" ), Change( nameof(InitializeSystem) )]\r\n\tpublic int MaxInstancesPerChunk { get; set; } = 500;\r\n\r\n\t[Property, Category( \"Performance\" ), Change( nameof(InitializeSystem) )]\r\n\tpublic List<MaterialInstanceOverride> MaterialOverrides { get; set; } = new();\r\n\r\n\t[Property, Category( \"Performance\" ), Change( nameof(InitializeSystem) )]\r\n\tpublic float ChunkSize { get; set; } = 500f;\r\n\r\n\t[Property, Category( \"Performance\" ), Change( nameof(InitializeSystem) )]\r\n\tpublic float ChunkLoadDistance { get; set; } = 3000f;\r\n\r\n\t[Property, Category( \"Performance\" )] public bool UseFrustumCulling { get; set; } = true;\r\n\t[Property, Category( \"Performance\" )] public float FrustumCullMinDistance { get; set; } = 800f;\r\n\t[Property, Category( \"Performance\" )] public bool AutomaticallyInitializeSystem { get; set; } = true;\r\n\r\n\t[Property, Category( \"Terrain\" ), Change( nameof(InitializeSystem) )]\r\n\tpublic bool UseTerrainNormal { get; set; } = true;\r\n\r\n\t[Property, Category( \"Seed\" ), Change( nameof(InitializeSystem) )]\r\n\tpublic int Seed { get; set; } = 12345;\r\n\r\n\t[Property, Category( \"Randomization\" ), Change( nameof(InitializeSystem) )]\r\n\tpublic bool RandomizeRotation { get; set; } = true;\r\n\r\n\tprivate readonly Dictionary<(int, int), ClutterChunk> _activeChunks = new();\r\n\tprivate readonly HashSet<(int, int)> _generatingChunks = new();\r\n\tprivate readonly List<(int, int)> _chunkRemovalList = new();\r\n\r\n\tprivate ushort[] _heightMapPixels;\r\n\tprivate uint[] _controlMapPixels;\r\n\tprivate int _resolution;\r\n\tprivate float _terrainSize;\r\n\tprivate float _terrainHeight;\r\n\tprivate float _maxRenderDistance;\r\n\tprivate float[][] _modelMaterialWeights;\r\n\tprivate GameObject _cachedHitObject;\r\n\tprivate bool _cachedIsObstruction;\r\n\tprivate Sandbox.Utility.INoiseField[] _modelNoiseFields;\r\n\tprivate Vector3 _lastCameraPos;\r\n\tprivate const float CameraUpdateThresholdSq = 10000f;\r\n\tprivate const float UshortMaxReciprocal = 1.0f / ushort.MaxValue;\r\n\tprivate SceneCustomObject _sceneObject;\r\n\r\n\tprivate ScattererModelEntry[] ModelEntries { get; set; }\r\n\tprivate float[] _modelRenderDistSq;\r\n\tprivate int[] _modelLodCounts;\r\n\tprivate float[][] _modelLodDistancesSq;\r\n\r\n\tprivate int[] _materialMaxInstances;\r\n\tprivate int _maxInstancesLoopCount;\r\n\r\n\tprivate ChunkRenderData[] _renderCache = Array.Empty<ChunkRenderData>();\r\n\r\n\t[ThreadStatic] private static List<Transform>[] _lodBuckets;\r\n\r\n\tprotected override void OnEnabled()\r\n\t{\r\n\t\tif ( !TargetTerrain.IsValid() )\r\n\t\t{\r\n\t\t\tTargetTerrain = Scene?.GetComponentInChildren<Terrain>();\r\n\t\t}\r\n\r\n\t\tCreateSceneObject();\r\n\t\tif ( AutomaticallyInitializeSystem )\r\n\t\t{\r\n\t\t\tInitializeSystem();\r\n\t\t}\r\n\t}\r\n\r\n\r\n\tprotected override void OnUpdate()\r\n\t{\r\n\t\tManageChunks();\r\n\t}\r\n\r\n\tprotected override void OnDisabled()\r\n\t{\r\n\t\t_sceneObject?.Delete();\r\n\t\t_sceneObject = null;\r\n\t}\r\n\r\n\tprivate void CreateSceneObject()\r\n\t{\r\n\t\t_sceneObject?.Delete();\r\n\t\tif ( Scene == null ) return;\r\n\r\n\t\t_sceneObject = new SceneCustomObject( Scene.SceneWorld )\r\n\t\t{\r\n\t\t\tRenderOverride = RenderClutter,\r\n\t\t\tTransform = new Transform( Vector3.Zero, Rotation.Identity, 1f ),\r\n\t\t\tFlags = { IsOpaque = true, IsTranslucent = false }\r\n\t\t};\r\n\t}\r\n\r\n\t[Button( \"Reinitialize\" )]\r\n\tpublic void InitializeSystem()\r\n\t{\r\n\t\t_activeChunks.Clear();\r\n\t\t_generatingChunks.Clear();\r\n\t\t_chunkRemovalList.Clear();\r\n\t\t_renderCache = Array.Empty<ChunkRenderData>();\r\n\t\tModelEntries = null;\r\n\t\t_modelRenderDistSq = null;\r\n\t\t_modelLodCounts = null;\r\n\t\t_modelLodDistancesSq = null;\r\n\r\n\t\tif ( Models == null || Models.Count == 0 ) return;\r\n\t\tif ( !TargetTerrain.IsValid() || TargetTerrain.Storage == null ) return;\r\n\r\n\t\t// --- CONFIGURATION VALIDATION WARNINGS ---\r\n\t\tvar seenModels = new HashSet<Model>();\r\n\t\tforeach ( var entry in Models )\r\n\t\t{\r\n\t\t\tif ( entry.Model != null && !seenModels.Add( entry.Model ) )\r\n\t\t\t{\r\n\t\t\t\tLog.Warning( $\"[Scatterer] Duplicate model entry found for '{entry.Model.ResourceName}' in the Models configuration list.\" );\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif ( MaterialOverrides != null )\r\n\t\t{\r\n\t\t\tvar seenOverrideMaterials = new HashSet<TerrainMaterial>();\r\n\t\t\tforeach ( var ov in MaterialOverrides )\r\n\t\t\t{\r\n\t\t\t\tif ( ov.Material != null && !seenOverrideMaterials.Add( ov.Material ) )\r\n\t\t\t\t{\r\n\t\t\t\t\tLog.Warning( $\"[Scatterer] Duplicate material override configuration found for material '{ov.Material.ResourceName}'.\" );\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tbool hasValidModel = false;\r\n\t\t_maxRenderDistance = ChunkLoadDistance;\r\n\r\n\t\tforeach ( var t in Models )\r\n\t\t{\r\n\t\t\tif ( t.Model != null )\r\n\t\t\t{\r\n\t\t\t\tvar seenMaterials = new HashSet<TerrainMaterial>();\r\n\t\t\t\tbool hasWeight = false;\r\n\r\n\t\t\t\t// MISSING NULL CHECK FIXED HERE:\r\n\t\t\t\tif ( t.MaterialWeights != null )\r\n\t\t\t\t{\r\n\t\t\t\t\tforeach ( var mw in t.MaterialWeights )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tif ( mw.Material == null ) continue;\r\n\r\n\t\t\t\t\t\tif ( !seenMaterials.Add( mw.Material ) )\r\n\t\t\t\t\t\t\tLog.Warning(\r\n\t\t\t\t\t\t\t\t$\"[Scatterer] Duplicate material entry '{mw.Material.ResourceName}' found for model '{t.Model.ResourceName}'.\" );\r\n\r\n\t\t\t\t\t\tif ( mw.Weight <= 0f )\r\n\t\t\t\t\t\t\tLog.Warning(\r\n\t\t\t\t\t\t\t\t$\"[Scatterer] Weight is set to 0 for material '{mw.Material.ResourceName}' on model '{t.Model.ResourceName}'.\" );\r\n\r\n\t\t\t\t\t\tif ( mw.Weight > 0f ) hasWeight = true;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif ( hasWeight ) hasValidModel = true;\r\n\r\n\t\t\t\tif ( t.RenderDistance > _maxRenderDistance )\r\n\t\t\t\t\t_maxRenderDistance = t.RenderDistance;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif ( !hasValidModel ) return;\r\n\r\n\t\tModelEntries = Models.ToArray();\r\n\t\t_modelRenderDistSq = new float[ModelEntries.Length];\r\n\t\t_modelLodCounts = new int[ModelEntries.Length];\r\n\t\t_modelLodDistancesSq = new float[ModelEntries.Length][];\r\n\r\n\t\tfor ( int m = 0; m < ModelEntries.Length; m++ )\r\n\t\t{\r\n\t\t\t_modelRenderDistSq[m] = ModelEntries[m].RenderDistance * ModelEntries[m].RenderDistance;\r\n\t\t\tvar mdl = ModelEntries[m].Model;\r\n\r\n\t\t\tif ( mdl != null )\r\n\t\t\t{\r\n\t\t\t\t_modelLodCounts[m] = Math.Max( 1, mdl.MeshInfo.LodCount );\r\n\t\t\t\tif ( mdl.MeshInfo.LodSwitchDistances != null && mdl.MeshInfo.LodSwitchDistances.Length > 0 &&\r\n\t\t\t\t     mdl.MeshInfo.LodSwitchDistances[0] > 0f )\r\n\t\t\t\t{\r\n\t\t\t\t\tvar switchDistances = mdl.MeshInfo.LodSwitchDistances;\r\n\t\t\t\t\t_modelLodDistancesSq[m] = new float[switchDistances.Length];\r\n\t\t\t\t\tfor ( int i = 0; i < switchDistances.Length; i++ )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tfloat dist = switchDistances[i] * ModelEntries[m].LodBias;\r\n\t\t\t\t\t\t_modelLodDistancesSq[m][i] = dist * dist;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\telse if ( _modelLodCounts[m] > 1 )\r\n\t\t\t\t{\r\n\t\t\t\t\tint switchCount = _modelLodCounts[m] - 1;\r\n\t\t\t\t\t_modelLodDistancesSq[m] = new float[switchCount];\r\n\t\t\t\t\tfloat step = ModelEntries[m].RenderDistance / _modelLodCounts[m];\r\n\t\t\t\t\tfor ( int i = 0; i < switchCount; i++ )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tfloat dist = (step * (i + 1)) * ModelEntries[m].LodBias;\r\n\t\t\t\t\t\t_modelLodDistancesSq[m][i] = dist * dist;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\t_modelLodDistancesSq[m] = Array.Empty<float>();\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\t_modelLodCounts[m] = 1;\r\n\t\t\t\t_modelLodDistancesSq[m] = Array.Empty<float>();\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t_heightMapPixels = TargetTerrain.Storage.HeightMap;\r\n\t\t_controlMapPixels = TargetTerrain.Storage.ControlMap;\r\n\t\t_resolution = TargetTerrain.Storage.Resolution;\r\n\t\t_terrainSize = TargetTerrain.TerrainSize;\r\n\t\t_terrainHeight = TargetTerrain.TerrainHeight;\r\n\r\n\t\t_modelMaterialWeights = new float[Models.Count][];\r\n\r\n\t\tfor ( int m = 0; m < Models.Count; m++ )\r\n\t\t{\r\n\t\t\t_modelMaterialWeights[m] = new float[32];\r\n\t\t\tif ( TargetTerrain.Storage.Materials != null && Models[m].MaterialWeights != null )\r\n\t\t\t{\r\n\t\t\t\tfor ( int i = 0; i < TargetTerrain.Storage.Materials.Count; i++ )\r\n\t\t\t\t{\r\n\t\t\t\t\tforeach ( var mw in Models[m].MaterialWeights )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tif ( mw.Material == TargetTerrain.Storage.Materials[i] )\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t_modelMaterialWeights[m][i] = mw.Weight;\r\n\t\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t_modelNoiseFields = new Sandbox.Utility.INoiseField[Models.Count];\r\n\t\tfor ( int m = 0; m < Models.Count; m++ )\r\n\t\t{\r\n\t\t\tvar modelEntry = Models[m];\r\n\t\t\tif ( modelEntry.NoiseType == ScattererNoiseType.None ) continue;\r\n\t\t\tvar parameters = new Sandbox.Utility.Noise.Parameters( Seed + m, modelEntry.NoiseScale );\r\n\r\n\t\t\tif ( modelEntry.NoiseType == ScattererNoiseType.Perlin )\r\n\t\t\t\t_modelNoiseFields[m] = Sandbox.Utility.Noise.PerlinField( parameters );\r\n\t\t\telse if ( modelEntry.NoiseType == ScattererNoiseType.Simplex )\r\n\t\t\t\t_modelNoiseFields[m] = Sandbox.Utility.Noise.SimplexField( parameters );\r\n\t\t}\r\n\r\n\t\t// --- MAPPING AND CEILING INITIALIZATION ---\r\n\t\t_materialMaxInstances = new int[32];\r\n\t\tArray.Fill( _materialMaxInstances, MaxInstancesPerChunk );\r\n\t\t_maxInstancesLoopCount = MaxInstancesPerChunk;\r\n\r\n\t\tif ( TargetTerrain.Storage.Materials != null && MaterialOverrides != null )\r\n\t\t{\r\n\t\t\tfor ( int i = 0; i < TargetTerrain.Storage.Materials.Count; i++ )\r\n\t\t\t{\r\n\t\t\t\tforeach ( var ov in MaterialOverrides )\r\n\t\t\t\t{\r\n\t\t\t\t\tif ( ov.Material == TargetTerrain.Storage.Materials[i] )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\t_materialMaxInstances[i] = ov.MaxInstances;\r\n\t\t\t\t\t\tif ( ov.MaxInstances > _maxInstancesLoopCount )\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t_maxInstancesLoopCount = ov.MaxInstances;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tRebuildRenderCache();\r\n\t}\r\n}\r\n"
        },
        {
            "Ident": "kivin.performantterrainscatterer",
            "Path": "PerformantTerrainScatterer/PerformantTerrainScattererManager.Data.cs",
            "FileName": "PerformantTerrainScattererManager.Data.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 293458,
            "IsPrivate": false,
            "Code": "using System.Collections.Generic;\r\nusing Sandbox;\r\n\r\npublic sealed partial class PerformantTerrainScatterer\r\n{\r\n\tpublic enum ScattererNoiseType\r\n\t{\r\n\t\tNone,\r\n\t\tPerlin,\r\n\t\tSimplex\r\n\t}\r\n\r\n\tpublic struct MaterialWeight\r\n\t{\r\n\t\tpublic MaterialWeight() { }\r\n\t\t[Property] public TerrainMaterial Material { get; set; } = null;\r\n\t\t[Property, Range( 0f, 100f )] public float Weight { get; set; } = 1f;\r\n\t}\r\n\r\n\tpublic struct MaterialInstanceOverride\r\n\t{\r\n\t\tpublic MaterialInstanceOverride() { }\r\n\t\t[Property] public TerrainMaterial Material { get; set; } = null;\r\n\t\t[Property] public int MaxInstances { get; set; } = 500;\r\n\t}\r\n\r\n\tpublic struct ScattererModelEntry\r\n\t{\r\n\t\tpublic ScattererModelEntry() { }\r\n\t\t[Property, Category( \"Base\" )] public Model Model { get; set; } = null;\r\n\t\t[Property, Category( \"Base\" )] public float ZOffset { get; set; } = 0f;\r\n\r\n\t\t[Property, Category( \"Noise Scattering\" )]\r\n\t\tpublic ScattererNoiseType NoiseType { get; set; } = ScattererNoiseType.None;\r\n\r\n\t\t[Property, Category( \"Noise Scattering\" )]\r\n\t\tpublic float NoiseScale { get; set; } = 0.002f;\r\n\r\n\t\t[Property, Category( \"Noise Scattering\" ), Range( 0f, 1f )]\r\n\t\tpublic float NoiseThreshold { get; set; } = 0.4f;\r\n\r\n\t\t[Property, Category( \"Performance\" )] public float RenderDistance { get; set; } = 2500f;\r\n\t\t[Property, Category( \"Terrain\" )] public List<MaterialWeight> MaterialWeights { get; set; } = new();\r\n\r\n\t\t[Property, Category( \"Randomization\" )]\r\n\t\tpublic bool RandomizeScale { get; set; } = false;\r\n\r\n\t\t[Property, Category( \"Randomization\" ), ShowIf( nameof(RandomizeScale), true )]\r\n\t\tpublic float MinScale { get; set; } = 0.8f;\r\n\r\n\t\t[Property, Category( \"Randomization\" ), ShowIf( nameof(RandomizeScale), true )]\r\n\t\tpublic float MaxScale { get; set; } = 1.2f;\r\n\r\n\t\t[Property, Category( \"LOD\" ), Range( 0.1f, 4f )]\r\n\t\tpublic float LodBias { get; set; } = 1.0f;\r\n\t}\r\n\r\n\tprivate struct ChunkRenderData\r\n\t{\r\n\t\tpublic Vector3 Center;\r\n\t\tpublic ModelRenderBatch[] Batches;\r\n\t}\r\n\r\n\tprivate struct ModelRenderBatch\r\n\t{\r\n\t\tpublic Model Model;\r\n\t\tpublic Transform[] Transforms;\r\n\t\tpublic float RenderDistSq;\r\n\t\tpublic float[] LodDistancesSq;\r\n\t\tpublic int MaxLod;\r\n\t}\r\n\r\n\tprivate struct InstanceData\r\n\t{\r\n\t\tpublic Vector3 Position;\r\n\t\tpublic Rotation Rotation;\r\n\t\tpublic float Scale;\r\n\t\tpublic int ModelIndex;\r\n\t}\r\n\r\n\tprivate class ClutterChunk\r\n\t{\r\n\t\tpublic Vector3 Center;\r\n\t\tpublic readonly Dictionary<int, Transform[]> TransformsByModel = new();\r\n\t}\r\n}\r\n"
        },
        {
            "Ident": "kivin.performantterrainscatterer",
            "Path": "PerformantTerrainScatterer/PerformantTerrainScattererManager.Rendering.cs",
            "FileName": "PerformantTerrainScattererManager.Rendering.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 293458,
            "IsPrivate": false,
            "Code": "using System;\r\nusing System.Collections.Generic;\r\nusing System.Runtime.InteropServices;\r\nusing Sandbox;\r\n\r\npublic sealed partial class PerformantTerrainScatterer\r\n{\r\n\tprivate void RebuildRenderCache()\r\n\t{\r\n\t\tvar newCache = new ChunkRenderData[_activeChunks.Count];\r\n\t\tint index = 0;\r\n\r\n\t\tforeach ( var chunk in _activeChunks.Values )\r\n\t\t{\r\n\t\t\tvar batches = new ModelRenderBatch[chunk.TransformsByModel.Count];\r\n\t\t\tint bIndex = 0;\r\n\r\n\t\t\tforeach ( var kvp in chunk.TransformsByModel )\r\n\t\t\t{\r\n\t\t\t\tint mIdx = kvp.Key;\r\n\t\t\t\tbatches[bIndex++] = new ModelRenderBatch\r\n\t\t\t\t{\r\n\t\t\t\t\tModel = ModelEntries[mIdx].Model,\r\n\t\t\t\t\tTransforms = kvp.Value,\r\n\t\t\t\t\tRenderDistSq = _modelRenderDistSq[mIdx],\r\n\t\t\t\t\tLodDistancesSq = _modelLodDistancesSq[mIdx],\r\n\t\t\t\t\tMaxLod = Math.Max( 0, _modelLodCounts[mIdx] - 1 )\r\n\t\t\t\t};\r\n\t\t\t}\r\n\r\n\t\t\tnewCache[index++] = new ChunkRenderData { Center = chunk.Center, Batches = batches };\r\n\t\t}\r\n\r\n\t\t_renderCache = newCache;\r\n\t}\r\n\r\n\tprivate void RenderClutter( SceneObject obj )\r\n\t{\r\n\t\tvar cache = _renderCache;\r\n\t\tif ( cache.Length == 0 ) return;\r\n\r\n\t\tif ( Scene == null ) return;\r\n\t\tvar camera = Scene.Camera;\r\n\t\tif ( !camera.IsValid() ) return;\r\n\r\n\t\tVector3 cameraPos = camera.WorldPosition;\r\n\t\tVector3 cameraForward = camera.WorldRotation.Forward;\r\n\r\n\t\tfloat frustumMinDistSq = FrustumCullMinDistance * FrustumCullMinDistance;\r\n\t\tfloat maxRenderDistSq = _maxRenderDistance * _maxRenderDistance;\r\n\t\tbool useFrustum = UseFrustumCulling;\r\n\r\n\t\tif ( _lodBuckets == null )\r\n\t\t{\r\n\t\t\t_lodBuckets = new List<Transform>[16];\r\n\t\t\tfor ( int i = 0; i < _lodBuckets.Length; i++ )\r\n\t\t\t\t_lodBuckets[i] = new List<Transform>();\r\n\t\t}\r\n\r\n\t\tfor ( int c = 0; c < cache.Length; c++ )\r\n\t\t{\r\n\t\t\tref var chunk = ref cache[c];\r\n\r\n\t\t\tVector3 toChunk = chunk.Center - cameraPos;\r\n\t\t\tfloat chunkDistSq = toChunk.LengthSquared;\r\n\r\n\t\t\tif ( chunkDistSq > maxRenderDistSq ) continue;\r\n\r\n\t\t\tif ( useFrustum && chunkDistSq > frustumMinDistSq )\r\n\t\t\t{\r\n\t\t\t\tfloat dot = Vector3.Dot( cameraForward, toChunk );\r\n\t\t\t\tif ( dot < 0f && (dot * dot) > 0.04f * chunkDistSq )\r\n\t\t\t\t\tcontinue;\r\n\t\t\t}\r\n\r\n\t\t\tvar batches = chunk.Batches;\r\n\t\t\tfor ( int b = 0; b < batches.Length; b++ )\r\n\t\t\t{\r\n\t\t\t\tref var batch = ref batches[b];\r\n\t\t\t\tif ( chunkDistSq > batch.RenderDistSq ) continue;\r\n\r\n\t\t\t\tint maxLod = batch.MaxLod;\r\n\r\n\t\t\t\tfor ( int i = 0; i <= maxLod; i++ )\r\n\t\t\t\t\t_lodBuckets[i].Clear();\r\n\r\n\t\t\t\tvar transforms = batch.Transforms;\r\n\t\t\t\tvar lodDists = batch.LodDistancesSq;\r\n\r\n\t\t\t\tfor ( int i = 0; i < transforms.Length; i++ )\r\n\t\t\t\t{\r\n\t\t\t\t\tVector3 instPos = transforms[i].Position;\r\n\t\t\t\t\tfloat instDistSq = (instPos - cameraPos).LengthSquared;\r\n\r\n\t\t\t\t\tif ( instDistSq > batch.RenderDistSq ) continue;\r\n\r\n\t\t\t\t\tint lod = 0;\r\n\t\t\t\t\tfor ( int l = 0; l < lodDists.Length; l++ )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tif ( instDistSq > lodDists[l] )\r\n\t\t\t\t\t\t\tlod = l + 1;\r\n\t\t\t\t\t\telse\r\n\t\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tif ( lod > maxLod ) lod = maxLod;\r\n\r\n\t\t\t\t\t_lodBuckets[lod].Add( transforms[i] );\r\n\t\t\t\t}\r\n\r\n\t\t\t\tfor ( int l = 0; l <= maxLod; l++ )\r\n\t\t\t\t{\r\n\t\t\t\t\tvar bucket = _lodBuckets[l];\r\n\t\t\t\t\tif ( bucket.Count > 0 )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tGraphics.DrawModelInstanced( batch.Model, CollectionsMarshal.AsSpan( bucket ), l );\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}\r\n"
        },
        {
            "Ident": "kivin.performantterrainscatterer",
            "Path": "Editor/MyEditorMenu.cs",
            "FileName": "MyEditorMenu.cs",
            "PackageType": "library",
            "CodeKind": "Editor",
            "AssetVersionId": 293458,
            "IsPrivate": false,
            "Code": "using Editor;\r\n\r\npublic static class MyEditorMenu\r\n{\r\n\t[Menu( \"Editor\", \"Performant Terrain Scatterer/My Menu Option\" )]\r\n\tpublic static void OpenMyMenu()\r\n\t{\r\n\t\tEditorUtility.DisplayDialog( \"It worked!\", \"This is being called from your library's editor code!\" );\r\n\t}\r\n}\r\n"
        },
        {
            "Ident": "kivin.performantterrainscatterer",
            "Path": "Code/PerformantTerrainScatterer/PerformantTerrainScattererManager.Data.cs",
            "FileName": "PerformantTerrainScattererManager.Data.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 293458,
            "IsPrivate": false,
            "Code": "using System.Collections.Generic;\r\nusing Sandbox;\r\n\r\npublic sealed partial class PerformantTerrainScatterer\r\n{\r\n\tpublic enum ScattererNoiseType\r\n\t{\r\n\t\tNone,\r\n\t\tPerlin,\r\n\t\tSimplex\r\n\t}\r\n\r\n\tpublic struct MaterialWeight\r\n\t{\r\n\t\tpublic MaterialWeight() { }\r\n\t\t[Property] public TerrainMaterial Material { get; set; } = null;\r\n\t\t[Property, Range( 0f, 100f )] public float Weight { get; set; } = 1f;\r\n\t}\r\n\r\n\tpublic struct MaterialInstanceOverride\r\n\t{\r\n\t\tpublic MaterialInstanceOverride() { }\r\n\t\t[Property] public TerrainMaterial Material { get; set; } = null;\r\n\t\t[Property] public int MaxInstances { get; set; } = 500;\r\n\t}\r\n\r\n\tpublic struct ScattererModelEntry\r\n\t{\r\n\t\tpublic ScattererModelEntry() { }\r\n\t\t[Property, Category( \"Base\" )] public Model Model { get; set; } = null;\r\n\t\t[Property, Category( \"Base\" )] public float ZOffset { get; set; } = 0f;\r\n\r\n\t\t[Property, Category( \"Noise Scattering\" )]\r\n\t\tpublic ScattererNoiseType NoiseType { get; set; } = ScattererNoiseType.None;\r\n\r\n\t\t[Property, Category( \"Noise Scattering\" )]\r\n\t\tpublic float NoiseScale { get; set; } = 0.002f;\r\n\r\n\t\t[Property, Category( \"Noise Scattering\" ), Range( 0f, 1f )]\r\n\t\tpublic float NoiseThreshold { get; set; } = 0.4f;\r\n\r\n\t\t[Property, Category( \"Performance\" )] public float RenderDistance { get; set; } = 2500f;\r\n\t\t[Property, Category( \"Terrain\" )] public List<MaterialWeight> MaterialWeights { get; set; } = new();\r\n\r\n\t\t[Property, Category( \"Randomization\" )]\r\n\t\tpublic bool RandomizeScale { get; set; } = false;\r\n\r\n\t\t[Property, Category( \"Randomization\" ), ShowIf( nameof(RandomizeScale), true )]\r\n\t\tpublic float MinScale { get; set; } = 0.8f;\r\n\r\n\t\t[Property, Category( \"Randomization\" ), ShowIf( nameof(RandomizeScale), true )]\r\n\t\tpublic float MaxScale { get; set; } = 1.2f;\r\n\r\n\t\t[Property, Category( \"LOD\" ), Range( 0.1f, 4f )]\r\n\t\tpublic float LodBias { get; set; } = 1.0f;\r\n\t}\r\n\r\n\tprivate struct ChunkRenderData\r\n\t{\r\n\t\tpublic Vector3 Center;\r\n\t\tpublic ModelRenderBatch[] Batches;\r\n\t}\r\n\r\n\tprivate struct ModelRenderBatch\r\n\t{\r\n\t\tpublic Model Model;\r\n\t\tpublic Transform[] Transforms;\r\n\t\tpublic float RenderDistSq;\r\n\t\tpublic float[] LodDistancesSq;\r\n\t\tpublic int MaxLod;\r\n\t}\r\n\r\n\tprivate struct InstanceData\r\n\t{\r\n\t\tpublic Vector3 Position;\r\n\t\tpublic Rotation Rotation;\r\n\t\tpublic float Scale;\r\n\t\tpublic int ModelIndex;\r\n\t}\r\n\r\n\tprivate class ClutterChunk\r\n\t{\r\n\t\tpublic Vector3 Center;\r\n\t\tpublic readonly Dictionary<int, Transform[]> TransformsByModel = new();\r\n\t}\r\n}\r\n"
        },
        {
            "Ident": "kivin.performantterrainscatterer",
            "Path": "PerformantTerrainScatterer/PerformantTerrainScattererManager.Placement.cs",
            "FileName": "PerformantTerrainScattererManager.Placement.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 293458,
            "IsPrivate": false,
            "Code": "using System;\r\nusing System.Collections.Generic;\r\nusing System.Runtime.CompilerServices;\r\nusing Sandbox;\r\n\r\npublic sealed partial class PerformantTerrainScatterer\r\n{\r\n\tprivate List<InstanceData> CalculateChunkPlacements( int cx, int cy, Transform terrainTransform )\r\n\t{\r\n\t\tif ( _heightMapPixels == null || _controlMapPixels == null ) return null;\r\n\t\tvar modelEntries = ModelEntries;\r\n\t\tif ( modelEntries == null || modelEntries.Length == 0 ) return null;\r\n\r\n\t\tint loopCount = _maxInstancesLoopCount;\r\n\t\tvar instances = new List<InstanceData>( loopCount );\r\n\t\tint chunkSeed = HashCombine( Seed, HashCombine( cx, cy ) );\r\n\t\tvar random = new Random( chunkSeed );\r\n\t\tfloat textureEps = 1.0f / (_resolution - 1);\r\n\t\tfloat chunkBaseX = cx * ChunkSize;\r\n\t\tfloat chunkBaseY = cy * ChunkSize;\r\n\t\tint modelsCount = modelEntries.Length;\r\n\r\n\t\tint[] placedCountPerMaterial = new int[32];\r\n\r\n\t\tfor ( int i = 0; i < loopCount; i++ )\r\n\t\t{\r\n\t\t\tfloat localX = random.NextSingle() * ChunkSize;\r\n\t\t\tfloat localY = random.NextSingle() * ChunkSize;\r\n\t\t\tfloat worldX = chunkBaseX + localX;\r\n\t\t\tfloat worldY = chunkBaseY + localY;\r\n\r\n\t\t\tVector3 worldAbsPoint = new Vector3( worldX, worldY, 0f );\r\n\t\t\tVector3 terrainLocal = terrainTransform.PointToLocal( worldAbsPoint );\r\n\t\t\tfloat u = terrainLocal.x / _terrainSize;\r\n\t\t\tfloat v = terrainLocal.y / _terrainSize;\r\n\r\n\t\t\tif ( u < 0f || u > 1f || v < 0f || v > 1f ) continue;\r\n\r\n\t\t\tint cX = (int)(u * (_resolution - 1f)).Clamp( 0f, _resolution - 1f );\r\n\t\t\tint cY = (int)(v * (_resolution - 1f)).Clamp( 0f, _resolution - 1f );\r\n\t\t\tint controlIndex = cX + cY * _resolution;\r\n\t\t\tuint packedMap = _controlMapPixels[controlIndex];\r\n\t\t\tvar compactMat = new CompactTerrainMaterial( packedMap );\r\n\r\n\t\t\tif ( compactMat.IsHole ) continue;\r\n\r\n\t\t\tint dominantMaterialId = (compactMat.BlendFactor > 127)\r\n\t\t\t\t? compactMat.OverlayTextureId\r\n\t\t\t\t: compactMat.BaseTextureId;\r\n\r\n\t\t\tif ( dominantMaterialId < 32 )\r\n\t\t\t{\r\n\t\t\t\tif ( placedCountPerMaterial[dominantMaterialId] >= _materialMaxInstances[dominantMaterialId] )\r\n\t\t\t\t\tcontinue;\r\n\t\t\t}\r\n\r\n\t\t\tfloat materialTotalWeight = 0f;\r\n\t\t\tfor ( int m = 0; m < modelsCount; m++ )\r\n\t\t\t{\r\n\t\t\t\tif ( dominantMaterialId < _modelMaterialWeights[m].Length )\r\n\t\t\t\t{\r\n\t\t\t\t\tmaterialTotalWeight += _modelMaterialWeights[m][dominantMaterialId];\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tif ( materialTotalWeight <= 0f ) continue;\r\n\r\n\t\t\tfloat roll = random.NextSingle() * materialTotalWeight;\r\n\t\t\tfloat currentWeightSum = 0f;\r\n\t\t\tint selectedModelIndex = -1;\r\n\r\n\t\t\tfor ( int m = 0; m < modelsCount; m++ )\r\n\t\t\t{\r\n\t\t\t\tif ( dominantMaterialId < _modelMaterialWeights[m].Length )\r\n\t\t\t\t{\r\n\t\t\t\t\tfloat w = _modelMaterialWeights[m][dominantMaterialId];\r\n\t\t\t\t\tif ( w > 0f )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tcurrentWeightSum += w;\r\n\t\t\t\t\t\tif ( roll <= currentWeightSum )\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tselectedModelIndex = m;\r\n\t\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tif ( selectedModelIndex == -1 ) continue;\r\n\t\t\tref var selectedModel = ref modelEntries[selectedModelIndex];\r\n\r\n\t\t\tif ( ReduceDensityOnTransitions )\r\n\t\t\t{\r\n\t\t\t\tfloat blendNorm = compactMat.BlendFactor * 0.003921569f;\r\n\t\t\t\tfloat purity = MathF.Abs( blendNorm - 0.5f ) * 2f;\r\n\t\t\t\tfloat spawnChance = MathF.Pow( purity, TransitionThinningIntensity );\r\n\t\t\t\tif ( random.NextSingle() > spawnChance ) continue;\r\n\t\t\t}\r\n\r\n\t\t\tif ( selectedModel.NoiseType != ScattererNoiseType.None )\r\n\t\t\t{\r\n\t\t\t\tvar noiseField = _modelNoiseFields[selectedModelIndex];\r\n\t\t\t\tfloat noiseVal = noiseField.Sample( worldX, worldY );\r\n\t\t\t\tif ( noiseVal < selectedModel.NoiseThreshold ) continue;\r\n\t\t\t}\r\n\r\n\t\t\tVector3 normal = Vector3.Up;\r\n\r\n\t\t\tif ( UseTerrainNormal || MaxSlopeAngle < 90f )\r\n\t\t\t{\r\n\t\t\t\tfloat hL = SampleHeightBilinear( u - textureEps, v );\r\n\t\t\t\tfloat hR = SampleHeightBilinear( u + textureEps, v );\r\n\t\t\t\tfloat hD = SampleHeightBilinear( u, v - textureEps );\r\n\t\t\t\tfloat hU = SampleHeightBilinear( u, v + textureEps );\r\n\t\t\t\tVector3 terrainTangentX =\r\n\t\t\t\t\tnew Vector3( 2.0f * textureEps * _terrainSize, 0f, (hR - hL) * _terrainHeight );\r\n\t\t\t\tVector3 terrainTangentY =\r\n\t\t\t\t\tnew Vector3( 0f, 2.0f * textureEps * _terrainSize, (hU - hD) * _terrainHeight );\r\n\t\t\t\tVector3 terrainNormalLocal = Vector3.Cross( terrainTangentX, terrainTangentY ).Normal;\r\n\t\t\t\tnormal = terrainTransform.Rotation * terrainNormalLocal;\r\n\t\t\t\tfloat slopeAngle = Vector3.GetAngle( Vector3.Up, normal );\r\n\t\t\t\tif ( slopeAngle > MaxSlopeAngle ) continue;\r\n\t\t\t}\r\n\r\n\t\t\tfloat z = SampleHeightBilinear( u, v ) * _terrainHeight;\r\n\t\t\tVector3 finalLocalPoint = new Vector3( terrainLocal.x, terrainLocal.y, z );\r\n\t\t\tVector3 worldTerrainPoint = terrainTransform.PointToWorld( finalLocalPoint );\r\n\r\n\t\t\tRotation randomYaw = RandomizeRotation\r\n\t\t\t\t? Rotation.FromYaw( random.NextSingle() * 360f )\r\n\t\t\t\t: Rotation.Identity;\r\n\r\n\t\t\tRotation finalRot = UseTerrainNormal\r\n\t\t\t\t? Rotation.FromToRotation( Vector3.Up, normal ) * randomYaw\r\n\t\t\t\t: randomYaw;\r\n\r\n\t\t\tfloat scale = selectedModel.RandomizeScale\r\n\t\t\t\t? (random.NextSingle() * (selectedModel.MaxScale - selectedModel.MinScale) + selectedModel.MinScale)\r\n\t\t\t\t: 1.0f;\r\n\r\n\t\t\tinstances.Add( new InstanceData\r\n\t\t\t{\r\n\t\t\t\tPosition = worldTerrainPoint, Rotation = finalRot, Scale = scale, ModelIndex = selectedModelIndex\r\n\t\t\t} );\r\n\r\n\t\t\tif ( dominantMaterialId < 32 )\r\n\t\t\t{\r\n\t\t\t\tplacedCountPerMaterial[dominantMaterialId]++;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn instances;\r\n\t}\r\n\r\n\t[MethodImpl( MethodImplOptions.AggressiveInlining )]\r\n\tprivate int HashCombine( int seed, int value )\r\n\t\t=> seed ^ (value + -1640531527 + (seed << 6) + (seed >> 2));\r\n\r\n\t[MethodImpl( MethodImplOptions.AggressiveInlining )]\r\n\tprivate bool IsObstruction( GameObject hitObject )\r\n\t{\r\n\t\tif ( hitObject == null ) return false;\r\n\t\tif ( _cachedHitObject == hitObject ) return _cachedIsObstruction;\r\n\t\t_cachedHitObject = hitObject;\r\n\t\t_cachedIsObstruction = hitObject.Components.Get<Terrain>() == null;\r\n\t\treturn _cachedIsObstruction;\r\n\t}\r\n\r\n\t[MethodImpl( MethodImplOptions.AggressiveInlining )]\r\n\tprivate float SampleHeightBilinear( float u, float v )\r\n\t{\r\n\t\tu = u.Clamp( 0f, 1f );\r\n\t\tv = v.Clamp( 0f, 1f );\r\n\t\tfloat gx = u * (_resolution - 1);\r\n\t\tfloat gy = v * (_resolution - 1);\r\n\t\tint ix = (int)gx;\r\n\t\tint iy = (int)gy;\r\n\t\tfloat fx = gx - ix;\r\n\t\tfloat fy = gy - iy;\r\n\t\tint ix1 = Math.Min( ix + 1, _resolution - 1 );\r\n\t\tint iy1 = Math.Min( iy + 1, _resolution - 1 );\r\n\t\tint row0 = iy * _resolution;\r\n\t\tint row1 = iy1 * _resolution;\r\n\t\tfloat h00 = _heightMapPixels[row0 + ix] * UshortMaxReciprocal;\r\n\t\tfloat h10 = _heightMapPixels[row0 + ix1] * UshortMaxReciprocal;\r\n\t\tfloat h01 = _heightMapPixels[row1 + ix] * UshortMaxReciprocal;\r\n\t\tfloat h11 = _heightMapPixels[row1 + ix1] * UshortMaxReciprocal;\r\n\t\tfloat h0 = h00 + (h10 - h00) * fx;\r\n\t\tfloat h1 = h01 + (h11 - h01) * fx;\r\n\t\treturn h0 + (h1 - h0) * fy;\r\n\t}\r\n}\r\n"
        },
        {
            "Ident": "kivin.performantterrainscatterer",
            "Path": "Code/PerformantTerrainScatterer/PerformantTerrainScattererManager.cs",
            "FileName": "PerformantTerrainScattererManager.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 293458,
            "IsPrivate": false,
            "Code": "using System;\r\nusing System.Collections.Generic;\r\nusing Sandbox;\r\n\r\npublic sealed partial class PerformantTerrainScatterer : Component, Component.ExecuteInEditor\r\n{\r\n\t[Property, Category( \"Terrain\" ), Change( nameof(InitializeSystem) )]\r\n\tpublic Terrain TargetTerrain { get; set; }\r\n\r\n\t[Property, Category( \"Models\" ), Change( nameof(InitializeSystem) )]\r\n\tpublic List<ScattererModelEntry> Models { get; set; } = new();\r\n\r\n\t[Property, Category( \"Terrain\" ), Change( nameof(InitializeSystem) ), Range( 0, 90 )]\r\n\tpublic float MaxSlopeAngle { get; set; } = 45f;\r\n\r\n\t[Property, Category( \"Terrain\" ), Change( nameof(InitializeSystem) )]\r\n\tpublic bool ReduceDensityOnTransitions { get; set; } = false;\r\n\r\n\t[Property, Category( \"Terrain\" ), Change( nameof(InitializeSystem) ),\r\n\t ShowIf( nameof(ReduceDensityOnTransitions), true )]\r\n\tpublic float TransitionThinningIntensity { get; set; } = 1.0f;\r\n\r\n\t[Property, Category( \"Terrain\" ), Change( nameof(InitializeSystem) )]\r\n\tpublic bool SnapToGround { get; set; } = true;\r\n\r\n\t[Property, Category( \"Collision\" ), Change( nameof(InitializeSystem) )]\r\n\tpublic bool CheckObstructions { get; set; } = true;\r\n\r\n\t[Property, Category( \"Collision\" ), Change( nameof(InitializeSystem) )]\r\n\tpublic float TraceStartHeight { get; set; } = 200f;\r\n\r\n\t[Property, Category( \"Collision\" ), Change( nameof(InitializeSystem) )]\r\n\tpublic float GridCellSize { get; set; } = 50f;\r\n\r\n\t[Property, Category( \"Collision\" ), Change( nameof(InitializeSystem) )]\r\n\tpublic int ObstructionBuffer { get; set; } = 2;\r\n\r\n\t[Property, Category( \"Collision\" )]\r\n\tpublic string[] TraceIgnoreTags { get; set; } = { \"player\", \"trigger\", \"clutter\" };\r\n\r\n\t[Property, Category( \"Performance\" ), Change( nameof(InitializeSystem) )]\r\n\tpublic int MaxInstancesPerChunk { get; set; } = 500;\r\n\r\n\t[Property, Category( \"Performance\" ), Change( nameof(InitializeSystem) )]\r\n\tpublic List<MaterialInstanceOverride> MaterialOverrides { get; set; } = new();\r\n\r\n\t[Property, Category( \"Performance\" ), Change( nameof(InitializeSystem) )]\r\n\tpublic float ChunkSize { get; set; } = 500f;\r\n\r\n\t[Property, Category( \"Performance\" ), Change( nameof(InitializeSystem) )]\r\n\tpublic float ChunkLoadDistance { get; set; } = 3000f;\r\n\r\n\t[Property, Category( \"Performance\" )] public bool UseFrustumCulling { get; set; } = true;\r\n\t[Property, Category( \"Performance\" )] public float FrustumCullMinDistance { get; set; } = 800f;\r\n\t[Property, Category( \"Performance\" )] public bool AutomaticallyInitializeSystem { get; set; } = true;\r\n\r\n\t[Property, Category( \"Terrain\" ), Change( nameof(InitializeSystem) )]\r\n\tpublic bool UseTerrainNormal { get; set; } = true;\r\n\r\n\t[Property, Category( \"Seed\" ), Change( nameof(InitializeSystem) )]\r\n\tpublic int Seed { get; set; } = 12345;\r\n\r\n\t[Property, Category( \"Randomization\" ), Change( nameof(InitializeSystem) )]\r\n\tpublic bool RandomizeRotation { get; set; } = true;\r\n\r\n\tprivate readonly Dictionary<(int, int), ClutterChunk> _activeChunks = new();\r\n\tprivate readonly HashSet<(int, int)> _generatingChunks = new();\r\n\tprivate readonly List<(int, int)> _chunkRemovalList = new();\r\n\r\n\tprivate ushort[] _heightMapPixels;\r\n\tprivate uint[] _controlMapPixels;\r\n\tprivate int _resolution;\r\n\tprivate float _terrainSize;\r\n\tprivate float _terrainHeight;\r\n\tprivate float _maxRenderDistance;\r\n\tprivate float[][] _modelMaterialWeights;\r\n\tprivate GameObject _cachedHitObject;\r\n\tprivate bool _cachedIsObstruction;\r\n\tprivate Sandbox.Utility.INoiseField[] _modelNoiseFields;\r\n\tprivate Vector3 _lastCameraPos;\r\n\tprivate const float CameraUpdateThresholdSq = 10000f;\r\n\tprivate const float UshortMaxReciprocal = 1.0f / ushort.MaxValue;\r\n\tprivate SceneCustomObject _sceneObject;\r\n\r\n\tprivate ScattererModelEntry[] ModelEntries { get; set; }\r\n\tprivate float[] _modelRenderDistSq;\r\n\tprivate int[] _modelLodCounts;\r\n\tprivate float[][] _modelLodDistancesSq;\r\n\r\n\tprivate int[] _materialMaxInstances;\r\n\tprivate int _maxInstancesLoopCount;\r\n\r\n\tprivate ChunkRenderData[] _renderCache = Array.Empty<ChunkRenderData>();\r\n\r\n\t[ThreadStatic] private static List<Transform>[] _lodBuckets;\r\n\r\n\tprotected override void OnEnabled()\r\n\t{\r\n\t\tif ( !TargetTerrain.IsValid() )\r\n\t\t{\r\n\t\t\tTargetTerrain = Scene?.GetComponentInChildren<Terrain>();\r\n\t\t}\r\n\r\n\t\tCreateSceneObject();\r\n\t\tif ( AutomaticallyInitializeSystem )\r\n\t\t{\r\n\t\t\tInitializeSystem();\r\n\t\t}\r\n\t}\r\n\r\n\r\n\tprotected override void OnUpdate()\r\n\t{\r\n\t\tManageChunks();\r\n\t}\r\n\r\n\tprotected override void OnDisabled()\r\n\t{\r\n\t\t_sceneObject?.Delete();\r\n\t\t_sceneObject = null;\r\n\t}\r\n\r\n\tprivate void CreateSceneObject()\r\n\t{\r\n\t\t_sceneObject?.Delete();\r\n\t\tif ( Scene == null ) return;\r\n\r\n\t\t_sceneObject = new SceneCustomObject( Scene.SceneWorld )\r\n\t\t{\r\n\t\t\tRenderOverride = RenderClutter,\r\n\t\t\tTransform = new Transform( Vector3.Zero, Rotation.Identity, 1f ),\r\n\t\t\tFlags = { IsOpaque = true, IsTranslucent = false }\r\n\t\t};\r\n\t}\r\n\r\n\t[Button( \"Reinitialize\" )]\r\n\tpublic void InitializeSystem()\r\n\t{\r\n\t\t_activeChunks.Clear();\r\n\t\t_generatingChunks.Clear();\r\n\t\t_chunkRemovalList.Clear();\r\n\t\t_renderCache = Array.Empty<ChunkRenderData>();\r\n\t\tModelEntries = null;\r\n\t\t_modelRenderDistSq = null;\r\n\t\t_modelLodCounts = null;\r\n\t\t_modelLodDistancesSq = null;\r\n\r\n\t\tif ( Models == null || Models.Count == 0 ) return;\r\n\t\tif ( !TargetTerrain.IsValid() || TargetTerrain.Storage == null ) return;\r\n\r\n\t\t// --- CONFIGURATION VALIDATION WARNINGS ---\r\n\t\tvar seenModels = new HashSet<Model>();\r\n\t\tforeach ( var entry in Models )\r\n\t\t{\r\n\t\t\tif ( entry.Model != null && !seenModels.Add( entry.Model ) )\r\n\t\t\t{\r\n\t\t\t\tLog.Warning( $\"[Scatterer] Duplicate model entry found for '{entry.Model.ResourceName}' in the Models configuration list.\" );\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif ( MaterialOverrides != null )\r\n\t\t{\r\n\t\t\tvar seenOverrideMaterials = new HashSet<TerrainMaterial>();\r\n\t\t\tforeach ( var ov in MaterialOverrides )\r\n\t\t\t{\r\n\t\t\t\tif ( ov.Material != null && !seenOverrideMaterials.Add( ov.Material ) )\r\n\t\t\t\t{\r\n\t\t\t\t\tLog.Warning( $\"[Scatterer] Duplicate material override configuration found for material '{ov.Material.ResourceName}'.\" );\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tbool hasValidModel = false;\r\n\t\t_maxRenderDistance = ChunkLoadDistance;\r\n\r\n\t\tforeach ( var t in Models )\r\n\t\t{\r\n\t\t\tif ( t.Model != null )\r\n\t\t\t{\r\n\t\t\t\tvar seenMaterials = new HashSet<TerrainMaterial>();\r\n\t\t\t\tbool hasWeight = false;\r\n\r\n\t\t\t\t// MISSING NULL CHECK FIXED HERE:\r\n\t\t\t\tif ( t.MaterialWeights != null )\r\n\t\t\t\t{\r\n\t\t\t\t\tforeach ( var mw in t.MaterialWeights )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tif ( mw.Material == null ) continue;\r\n\r\n\t\t\t\t\t\tif ( !seenMaterials.Add( mw.Material ) )\r\n\t\t\t\t\t\t\tLog.Warning(\r\n\t\t\t\t\t\t\t\t$\"[Scatterer] Duplicate material entry '{mw.Material.ResourceName}' found for model '{t.Model.ResourceName}'.\" );\r\n\r\n\t\t\t\t\t\tif ( mw.Weight <= 0f )\r\n\t\t\t\t\t\t\tLog.Warning(\r\n\t\t\t\t\t\t\t\t$\"[Scatterer] Weight is set to 0 for material '{mw.Material.ResourceName}' on model '{t.Model.ResourceName}'.\" );\r\n\r\n\t\t\t\t\t\tif ( mw.Weight > 0f ) hasWeight = true;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif ( hasWeight ) hasValidModel = true;\r\n\r\n\t\t\t\tif ( t.RenderDistance > _maxRenderDistance )\r\n\t\t\t\t\t_maxRenderDistance = t.RenderDistance;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif ( !hasValidModel ) return;\r\n\r\n\t\tModelEntries = Models.ToArray();\r\n\t\t_modelRenderDistSq = new float[ModelEntries.Length];\r\n\t\t_modelLodCounts = new int[ModelEntries.Length];\r\n\t\t_modelLodDistancesSq = new float[ModelEntries.Length][];\r\n\r\n\t\tfor ( int m = 0; m < ModelEntries.Length; m++ )\r\n\t\t{\r\n\t\t\t_modelRenderDistSq[m] = ModelEntries[m].RenderDistance * ModelEntries[m].RenderDistance;\r\n\t\t\tvar mdl = ModelEntries[m].Model;\r\n\r\n\t\t\tif ( mdl != null )\r\n\t\t\t{\r\n\t\t\t\t_modelLodCounts[m] = Math.Max( 1, mdl.MeshInfo.LodCount );\r\n\t\t\t\tif ( mdl.MeshInfo.LodSwitchDistances != null && mdl.MeshInfo.LodSwitchDistances.Length > 0 &&\r\n\t\t\t\t     mdl.MeshInfo.LodSwitchDistances[0] > 0f )\r\n\t\t\t\t{\r\n\t\t\t\t\tvar switchDistances = mdl.MeshInfo.LodSwitchDistances;\r\n\t\t\t\t\t_modelLodDistancesSq[m] = new float[switchDistances.Length];\r\n\t\t\t\t\tfor ( int i = 0; i < switchDistances.Length; i++ )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tfloat dist = switchDistances[i] * ModelEntries[m].LodBias;\r\n\t\t\t\t\t\t_modelLodDistancesSq[m][i] = dist * dist;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\telse if ( _modelLodCounts[m] > 1 )\r\n\t\t\t\t{\r\n\t\t\t\t\tint switchCount = _modelLodCounts[m] - 1;\r\n\t\t\t\t\t_modelLodDistancesSq[m] = new float[switchCount];\r\n\t\t\t\t\tfloat step = ModelEntries[m].RenderDistance / _modelLodCounts[m];\r\n\t\t\t\t\tfor ( int i = 0; i < switchCount; i++ )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tfloat dist = (step * (i + 1)) * ModelEntries[m].LodBias;\r\n\t\t\t\t\t\t_modelLodDistancesSq[m][i] = dist * dist;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\t_modelLodDistancesSq[m] = Array.Empty<float>();\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\t_modelLodCounts[m] = 1;\r\n\t\t\t\t_modelLodDistancesSq[m] = Array.Empty<float>();\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t_heightMapPixels = TargetTerrain.Storage.HeightMap;\r\n\t\t_controlMapPixels = TargetTerrain.Storage.ControlMap;\r\n\t\t_resolution = TargetTerrain.Storage.Resolution;\r\n\t\t_terrainSize = TargetTerrain.TerrainSize;\r\n\t\t_terrainHeight = TargetTerrain.TerrainHeight;\r\n\r\n\t\t_modelMaterialWeights = new float[Models.Count][];\r\n\r\n\t\tfor ( int m = 0; m < Models.Count; m++ )\r\n\t\t{\r\n\t\t\t_modelMaterialWeights[m] = new float[32];\r\n\t\t\tif ( TargetTerrain.Storage.Materials != null && Models[m].MaterialWeights != null )\r\n\t\t\t{\r\n\t\t\t\tfor ( int i = 0; i < TargetTerrain.Storage.Materials.Count; i++ )\r\n\t\t\t\t{\r\n\t\t\t\t\tforeach ( var mw in Models[m].MaterialWeights )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tif ( mw.Material == TargetTerrain.Storage.Materials[i] )\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t_modelMaterialWeights[m][i] = mw.Weight;\r\n\t\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t_modelNoiseFields = new Sandbox.Utility.INoiseField[Models.Count];\r\n\t\tfor ( int m = 0; m < Models.Count; m++ )\r\n\t\t{\r\n\t\t\tvar modelEntry = Models[m];\r\n\t\t\tif ( modelEntry.NoiseType == ScattererNoiseType.None ) continue;\r\n\t\t\tvar parameters = new Sandbox.Utility.Noise.Parameters( Seed + m, modelEntry.NoiseScale );\r\n\r\n\t\t\tif ( modelEntry.NoiseType == ScattererNoiseType.Perlin )\r\n\t\t\t\t_modelNoiseFields[m] = Sandbox.Utility.Noise.PerlinField( parameters );\r\n\t\t\telse if ( modelEntry.NoiseType == ScattererNoiseType.Simplex )\r\n\t\t\t\t_modelNoiseFields[m] = Sandbox.Utility.Noise.SimplexField( parameters );\r\n\t\t}\r\n\r\n\t\t// --- MAPPING AND CEILING INITIALIZATION ---\r\n\t\t_materialMaxInstances = new int[32];\r\n\t\tArray.Fill( _materialMaxInstances, MaxInstancesPerChunk );\r\n\t\t_maxInstancesLoopCount = MaxInstancesPerChunk;\r\n\r\n\t\tif ( TargetTerrain.Storage.Materials != null && MaterialOverrides != null )\r\n\t\t{\r\n\t\t\tfor ( int i = 0; i < TargetTerrain.Storage.Materials.Count; i++ )\r\n\t\t\t{\r\n\t\t\t\tforeach ( var ov in MaterialOverrides )\r\n\t\t\t\t{\r\n\t\t\t\t\tif ( ov.Material == TargetTerrain.Storage.Materials[i] )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\t_materialMaxInstances[i] = ov.MaxInstances;\r\n\t\t\t\t\t\tif ( ov.MaxInstances > _maxInstancesLoopCount )\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t_maxInstancesLoopCount = ov.MaxInstances;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tRebuildRenderCache();\r\n\t}\r\n}\r\n"
        },
        {
            "Ident": "kivin.performantterrainscatterer",
            "Path": "UnitTests/LibraryTest.cs",
            "FileName": "LibraryTest.cs",
            "PackageType": "library",
            "CodeKind": "UnitTest",
            "AssetVersionId": 293458,
            "IsPrivate": false,
            "Code": "using Sandbox;\r\n\r\n[TestClass]\r\npublic partial class LibraryTests\r\n{\r\n\t[TestMethod]\r\n\tpublic void SceneTest()\r\n\t{\r\n\t\tvar scene = new Scene();\r\n\t\tusing ( scene.Push() )\r\n\t\t{\r\n\t\t\tvar go = new GameObject();\r\n\r\n\t\t\tAssert.AreEqual( 1, scene.Directory.GameObjectCount );\r\n\t\t}\r\n\t}\r\n\r\n}\r\n"
        },
        {
            "Ident": "kivin.performantterrainscatterer",
            "Path": "Code/PerformantTerrainScatterer/PerformantTerrainScattererManager.Chunks.cs",
            "FileName": "PerformantTerrainScattererManager.Chunks.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 293458,
            "IsPrivate": false,
            "Code": "using System;\r\nusing System.Buffers;\r\nusing System.Collections.Generic;\r\nusing System.Threading.Tasks;\r\nusing Sandbox;\r\n\r\npublic sealed partial class PerformantTerrainScatterer\r\n{\r\n\tprivate void ManageChunks()\r\n\t{\r\n\t\tif ( Scene == null ) return;\r\n\t\tvar camera = Scene.Camera;\r\n\t\tif ( !camera.IsValid() ) return;\r\n\t\tVector3 cameraPos = camera.WorldPosition;\r\n\r\n\t\tif ( _activeChunks.Count > 0 && cameraPos.DistanceSquared( _lastCameraPos ) < CameraUpdateThresholdSq )\r\n\t\t\treturn;\r\n\r\n\t\t_lastCameraPos = cameraPos;\r\n\t\tint cameraCx = (int)MathF.Floor( cameraPos.x / ChunkSize );\r\n\t\tint cameraCy = (int)MathF.Floor( cameraPos.y / ChunkSize );\r\n\t\tint chunksInRadius = (int)MathF.Ceiling( ChunkLoadDistance / ChunkSize );\r\n\r\n\t\tfor ( int dx = -chunksInRadius; dx <= chunksInRadius; dx++ )\r\n\t\t{\r\n\t\t\tfor ( int dy = -chunksInRadius; dy <= chunksInRadius; dy++ )\r\n\t\t\t{\r\n\t\t\t\tvar chunkCoord = (cameraCx + dx, cameraCy + dy);\r\n\t\t\t\tif ( !_activeChunks.ContainsKey( chunkCoord ) && _generatingChunks.Add( chunkCoord ) )\r\n\t\t\t\t\t_ = GenerateChunkAsync( chunkCoord.Item1, chunkCoord.Item2 );\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t_chunkRemovalList.Clear();\r\n\t\tforeach ( var key in _activeChunks.Keys )\r\n\t\t{\r\n\t\t\tif ( Math.Abs( key.Item1 - cameraCx ) > chunksInRadius ||\r\n\t\t\t     Math.Abs( key.Item2 - cameraCy ) > chunksInRadius )\r\n\t\t\t\t_chunkRemovalList.Add( key );\r\n\t\t}\r\n\r\n\t\tforeach ( var t in _chunkRemovalList )\r\n\t\t\t_activeChunks.Remove( t );\r\n\r\n\t\tif ( _chunkRemovalList.Count > 0 )\r\n\t\t\tRebuildRenderCache();\r\n\t}\r\n\r\n\tprivate async Task GenerateChunkAsync( int cx, int cy )\r\n\t{\r\n\t\ttry\r\n\t\t{\r\n\t\t\tif ( !IsValid || Scene == null ) return;\r\n\t\t\tTransform terrainTransform = TargetTerrain.IsValid() ? TargetTerrain.WorldTransform : new Transform();\r\n\r\n\t\t\tvar tentativeInstances =\r\n\t\t\t\tawait GameTask.RunInThreadAsync( () => CalculateChunkPlacements( cx, cy, terrainTransform ) );\r\n\r\n\t\t\tif ( !IsValid || Scene == null ) return;\r\n\r\n\t\t\tvar modelEntries = ModelEntries;\r\n\t\t\tif ( tentativeInstances == null || modelEntries == null ) return;\r\n\r\n\t\t\tint cellsPerChunk = (int)MathF.Ceiling( ChunkSize / GridCellSize );\r\n\t\t\tint gridPadding = ObstructionBuffer;\r\n\t\t\tint gridWidth = cellsPerChunk + (gridPadding * 2) + 2;\r\n\t\t\tint gridArea = gridWidth * gridWidth;\r\n\r\n\t\t\tbool[] localBlockedGrid = ArrayPool<bool>.Shared.Rent( gridArea );\r\n\t\t\tArray.Clear( localBlockedGrid, 0, gridArea );\r\n\r\n\t\t\ttry\r\n\t\t\t{\r\n\t\t\t\tint minGridX = (int)MathF.Floor( (cx * ChunkSize) / GridCellSize ) - gridPadding;\r\n\t\t\t\tint minGridY = (int)MathF.Floor( (cy * ChunkSize) / GridCellSize ) - gridPadding;\r\n\t\t\t\tint traceCounter = 0;\r\n\t\t\t\tbool needsTrace = CheckObstructions || SnapToGround;\r\n\t\t\t\tList<Transform>[] modelLists = new List<Transform>[modelEntries.Length];\r\n\t\t\t\tfloat cxSum = 0f, cySum = 0f, czSum = 0f;\r\n\t\t\t\tint placedCount = 0;\r\n\t\t\t\tVector3 traceUpOffset = Vector3.Up * TraceStartHeight;\r\n\r\n\t\t\t\tforeach ( var inst in tentativeInstances )\r\n\t\t\t\t{\r\n\t\t\t\t\tif ( !IsValid || Scene == null ) return;\r\n\r\n\t\t\t\t\tVector3 finalPosition = inst.Position;\r\n\t\t\t\t\tint gridX = (int)MathF.Floor( inst.Position.x / GridCellSize );\r\n\t\t\t\t\tint gridY = (int)MathF.Floor( inst.Position.y / GridCellSize );\r\n\t\t\t\t\tint localX = gridX - minGridX;\r\n\t\t\t\t\tint localY = gridY - minGridY;\r\n\r\n\t\t\t\t\tif ( CheckObstructions )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tbool isBlocked = false;\r\n\t\t\t\t\t\tfor ( int dx = -ObstructionBuffer; dx <= ObstructionBuffer; dx++ )\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tint nx = localX + dx;\r\n\t\t\t\t\t\t\tif ( nx < 0 || nx >= gridWidth ) continue;\r\n\t\t\t\t\t\t\tfor ( int dy = -ObstructionBuffer; dy <= ObstructionBuffer; dy++ )\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\tint ny = localY + dy;\r\n\t\t\t\t\t\t\t\tif ( ny < 0 || ny >= gridWidth ) continue;\r\n\t\t\t\t\t\t\t\tif ( localBlockedGrid[nx + ny * gridWidth] )\r\n\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\tisBlocked = true;\r\n\t\t\t\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\t\tif ( isBlocked ) break;\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\tif ( isBlocked ) continue;\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tif ( needsTrace )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar traceStart = inst.Position + traceUpOffset;\r\n\t\t\t\t\t\tvar traceEnd = inst.Position - traceUpOffset;\r\n\t\t\t\t\t\tvar trace = Scene.Trace.Ray( traceStart, traceEnd )\r\n\t\t\t\t\t\t\t.WithoutTags( TraceIgnoreTags )\r\n\t\t\t\t\t\t\t.Run();\r\n\r\n\t\t\t\t\t\tbool hitObstruction = trace.Hit && IsObstruction( trace.GameObject );\r\n\r\n\t\t\t\t\t\tif ( CheckObstructions && (trace.StartedSolid || hitObstruction) )\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tif ( localX >= 0 && localX < gridWidth && localY >= 0 && localY < gridWidth )\r\n\t\t\t\t\t\t\t\tlocalBlockedGrid[localX + localY * gridWidth] = true;\r\n\t\t\t\t\t\t\tcontinue;\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\tif ( SnapToGround && trace.Hit && !trace.StartedSolid && !hitObstruction )\r\n\t\t\t\t\t\t\tfinalPosition = trace.HitPosition;\r\n\r\n\t\t\t\t\t\ttraceCounter++;\r\n\t\t\t\t\t\tif ( traceCounter % 100 == 0 )\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tawait GameTask.Yield();\r\n\t\t\t\t\t\t\tif ( !IsValid || Scene == null ) return;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tif ( inst.ModelIndex < 0 || inst.ModelIndex >= modelEntries.Length ) continue;\r\n\r\n\t\t\t\t\tfinalPosition += Vector3.Up * modelEntries[inst.ModelIndex].ZOffset;\r\n\t\t\t\t\tvar list = modelLists[inst.ModelIndex] ??= new List<Transform>();\r\n\t\t\t\t\tlist.Add( new Transform( finalPosition, inst.Rotation, inst.Scale ) );\r\n\r\n\t\t\t\t\tcxSum += finalPosition.x;\r\n\t\t\t\t\tcySum += finalPosition.y;\r\n\t\t\t\t\tczSum += finalPosition.z;\r\n\t\t\t\t\tplacedCount++;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif ( !IsValid ) return;\r\n\r\n\t\t\t\tvar chunk = new ClutterChunk();\r\n\t\t\t\tchunk.Center = placedCount > 0\r\n\t\t\t\t\t? new Vector3( cxSum / placedCount, cySum / placedCount, czSum / placedCount )\r\n\t\t\t\t\t: new Vector3( cx * ChunkSize + ChunkSize * 0.5f, cy * ChunkSize + ChunkSize * 0.5f, 0f );\r\n\r\n\t\t\t\tfor ( int i = 0; i < modelLists.Length; i++ )\r\n\t\t\t\t{\r\n\t\t\t\t\tif ( modelLists[i] != null && modelLists[i].Count > 0 )\r\n\t\t\t\t\t\tchunk.TransformsByModel[i] = modelLists[i].ToArray();\r\n\t\t\t\t}\r\n\r\n\t\t\t\t_activeChunks[(cx, cy)] = chunk;\r\n\t\t\t\tRebuildRenderCache();\r\n\t\t\t}\r\n\t\t\tfinally\r\n\t\t\t{\r\n\t\t\t\tArrayPool<bool>.Shared.Return( localBlockedGrid );\r\n\t\t\t}\r\n\t\t}\r\n\t\tcatch ( Exception )\r\n\t\t{\r\n\t\t}\r\n\t\tfinally\r\n\t\t{\r\n\t\t\t_generatingChunks.Remove( (cx, cy) );\r\n\t\t}\r\n\t}\r\n}\r\n"
        },
        {
            "Ident": "kivin.performantterrainscatterer",
            "Path": "Code/PerformantTerrainScatterer/PerformantTerrainScattererManager.Rendering.cs",
            "FileName": "PerformantTerrainScattererManager.Rendering.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 293458,
            "IsPrivate": false,
            "Code": "using System;\r\nusing System.Collections.Generic;\r\nusing System.Runtime.InteropServices;\r\nusing Sandbox;\r\n\r\npublic sealed partial class PerformantTerrainScatterer\r\n{\r\n\tprivate void RebuildRenderCache()\r\n\t{\r\n\t\tvar newCache = new ChunkRenderData[_activeChunks.Count];\r\n\t\tint index = 0;\r\n\r\n\t\tforeach ( var chunk in _activeChunks.Values )\r\n\t\t{\r\n\t\t\tvar batches = new ModelRenderBatch[chunk.TransformsByModel.Count];\r\n\t\t\tint bIndex = 0;\r\n\r\n\t\t\tforeach ( var kvp in chunk.TransformsByModel )\r\n\t\t\t{\r\n\t\t\t\tint mIdx = kvp.Key;\r\n\t\t\t\tbatches[bIndex++] = new ModelRenderBatch\r\n\t\t\t\t{\r\n\t\t\t\t\tModel = ModelEntries[mIdx].Model,\r\n\t\t\t\t\tTransforms = kvp.Value,\r\n\t\t\t\t\tRenderDistSq = _modelRenderDistSq[mIdx],\r\n\t\t\t\t\tLodDistancesSq = _modelLodDistancesSq[mIdx],\r\n\t\t\t\t\tMaxLod = Math.Max( 0, _modelLodCounts[mIdx] - 1 )\r\n\t\t\t\t};\r\n\t\t\t}\r\n\r\n\t\t\tnewCache[index++] = new ChunkRenderData { Center = chunk.Center, Batches = batches };\r\n\t\t}\r\n\r\n\t\t_renderCache = newCache;\r\n\t}\r\n\r\n\tprivate void RenderClutter( SceneObject obj )\r\n\t{\r\n\t\tvar cache = _renderCache;\r\n\t\tif ( cache.Length == 0 ) return;\r\n\r\n\t\tif ( Scene == null ) return;\r\n\t\tvar camera = Scene.Camera;\r\n\t\tif ( !camera.IsValid() ) return;\r\n\r\n\t\tVector3 cameraPos = camera.WorldPosition;\r\n\t\tVector3 cameraForward = camera.WorldRotation.Forward;\r\n\r\n\t\tfloat frustumMinDistSq = FrustumCullMinDistance * FrustumCullMinDistance;\r\n\t\tfloat maxRenderDistSq = _maxRenderDistance * _maxRenderDistance;\r\n\t\tbool useFrustum = UseFrustumCulling;\r\n\r\n\t\tif ( _lodBuckets == null )\r\n\t\t{\r\n\t\t\t_lodBuckets = new List<Transform>[16];\r\n\t\t\tfor ( int i = 0; i < _lodBuckets.Length; i++ )\r\n\t\t\t\t_lodBuckets[i] = new List<Transform>();\r\n\t\t}\r\n\r\n\t\tfor ( int c = 0; c < cache.Length; c++ )\r\n\t\t{\r\n\t\t\tref var chunk = ref cache[c];\r\n\r\n\t\t\tVector3 toChunk = chunk.Center - cameraPos;\r\n\t\t\tfloat chunkDistSq = toChunk.LengthSquared;\r\n\r\n\t\t\tif ( chunkDistSq > maxRenderDistSq ) continue;\r\n\r\n\t\t\tif ( useFrustum && chunkDistSq > frustumMinDistSq )\r\n\t\t\t{\r\n\t\t\t\tfloat dot = Vector3.Dot( cameraForward, toChunk );\r\n\t\t\t\tif ( dot < 0f && (dot * dot) > 0.04f * chunkDistSq )\r\n\t\t\t\t\tcontinue;\r\n\t\t\t}\r\n\r\n\t\t\tvar batches = chunk.Batches;\r\n\t\t\tfor ( int b = 0; b < batches.Length; b++ )\r\n\t\t\t{\r\n\t\t\t\tref var batch = ref batches[b];\r\n\t\t\t\tif ( chunkDistSq > batch.RenderDistSq ) continue;\r\n\r\n\t\t\t\tint maxLod = batch.MaxLod;\r\n\r\n\t\t\t\tfor ( int i = 0; i <= maxLod; i++ )\r\n\t\t\t\t\t_lodBuckets[i].Clear();\r\n\r\n\t\t\t\tvar transforms = batch.Transforms;\r\n\t\t\t\tvar lodDists = batch.LodDistancesSq;\r\n\r\n\t\t\t\tfor ( int i = 0; i < transforms.Length; i++ )\r\n\t\t\t\t{\r\n\t\t\t\t\tVector3 instPos = transforms[i].Position;\r\n\t\t\t\t\tfloat instDistSq = (instPos - cameraPos).LengthSquared;\r\n\r\n\t\t\t\t\tif ( instDistSq > batch.RenderDistSq ) continue;\r\n\r\n\t\t\t\t\tint lod = 0;\r\n\t\t\t\t\tfor ( int l = 0; l < lodDists.Length; l++ )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tif ( instDistSq > lodDists[l] )\r\n\t\t\t\t\t\t\tlod = l + 1;\r\n\t\t\t\t\t\telse\r\n\t\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tif ( lod > maxLod ) lod = maxLod;\r\n\r\n\t\t\t\t\t_lodBuckets[lod].Add( transforms[i] );\r\n\t\t\t\t}\r\n\r\n\t\t\t\tfor ( int l = 0; l <= maxLod; l++ )\r\n\t\t\t\t{\r\n\t\t\t\t\tvar bucket = _lodBuckets[l];\r\n\t\t\t\t\tif ( bucket.Count > 0 )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tGraphics.DrawModelInstanced( batch.Model, CollectionsMarshal.AsSpan( bucket ), l );\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}\r\n"
        },
        {
            "Ident": "kivin.performantterrainscatterer",
            "Path": "Code/PerformantTerrainScatterer/PerformantTerrainScattererManager.Placement.cs",
            "FileName": "PerformantTerrainScattererManager.Placement.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 293458,
            "IsPrivate": false,
            "Code": "using System;\r\nusing System.Collections.Generic;\r\nusing System.Runtime.CompilerServices;\r\nusing Sandbox;\r\n\r\npublic sealed partial class PerformantTerrainScatterer\r\n{\r\n\tprivate List<InstanceData> CalculateChunkPlacements( int cx, int cy, Transform terrainTransform )\r\n\t{\r\n\t\tif ( _heightMapPixels == null || _controlMapPixels == null ) return null;\r\n\t\tvar modelEntries = ModelEntries;\r\n\t\tif ( modelEntries == null || modelEntries.Length == 0 ) return null;\r\n\r\n\t\tint loopCount = _maxInstancesLoopCount;\r\n\t\tvar instances = new List<InstanceData>( loopCount );\r\n\t\tint chunkSeed = HashCombine( Seed, HashCombine( cx, cy ) );\r\n\t\tvar random = new Random( chunkSeed );\r\n\t\tfloat textureEps = 1.0f / (_resolution - 1);\r\n\t\tfloat chunkBaseX = cx * ChunkSize;\r\n\t\tfloat chunkBaseY = cy * ChunkSize;\r\n\t\tint modelsCount = modelEntries.Length;\r\n\r\n\t\tint[] placedCountPerMaterial = new int[32];\r\n\r\n\t\tfor ( int i = 0; i < loopCount; i++ )\r\n\t\t{\r\n\t\t\tfloat localX = random.NextSingle() * ChunkSize;\r\n\t\t\tfloat localY = random.NextSingle() * ChunkSize;\r\n\t\t\tfloat worldX = chunkBaseX + localX;\r\n\t\t\tfloat worldY = chunkBaseY + localY;\r\n\r\n\t\t\tVector3 worldAbsPoint = new Vector3( worldX, worldY, 0f );\r\n\t\t\tVector3 terrainLocal = terrainTransform.PointToLocal( worldAbsPoint );\r\n\t\t\tfloat u = terrainLocal.x / _terrainSize;\r\n\t\t\tfloat v = terrainLocal.y / _terrainSize;\r\n\r\n\t\t\tif ( u < 0f || u > 1f || v < 0f || v > 1f ) continue;\r\n\r\n\t\t\tint cX = (int)(u * (_resolution - 1f)).Clamp( 0f, _resolution - 1f );\r\n\t\t\tint cY = (int)(v * (_resolution - 1f)).Clamp( 0f, _resolution - 1f );\r\n\t\t\tint controlIndex = cX + cY * _resolution;\r\n\t\t\tuint packedMap = _controlMapPixels[controlIndex];\r\n\t\t\tvar compactMat = new CompactTerrainMaterial( packedMap );\r\n\r\n\t\t\tif ( compactMat.IsHole ) continue;\r\n\r\n\t\t\tint dominantMaterialId = (compactMat.BlendFactor > 127)\r\n\t\t\t\t? compactMat.OverlayTextureId\r\n\t\t\t\t: compactMat.BaseTextureId;\r\n\r\n\t\t\tif ( dominantMaterialId < 32 )\r\n\t\t\t{\r\n\t\t\t\tif ( placedCountPerMaterial[dominantMaterialId] >= _materialMaxInstances[dominantMaterialId] )\r\n\t\t\t\t\tcontinue;\r\n\t\t\t}\r\n\r\n\t\t\tfloat materialTotalWeight = 0f;\r\n\t\t\tfor ( int m = 0; m < modelsCount; m++ )\r\n\t\t\t{\r\n\t\t\t\tif ( dominantMaterialId < _modelMaterialWeights[m].Length )\r\n\t\t\t\t{\r\n\t\t\t\t\tmaterialTotalWeight += _modelMaterialWeights[m][dominantMaterialId];\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tif ( materialTotalWeight <= 0f ) continue;\r\n\r\n\t\t\tfloat roll = random.NextSingle() * materialTotalWeight;\r\n\t\t\tfloat currentWeightSum = 0f;\r\n\t\t\tint selectedModelIndex = -1;\r\n\r\n\t\t\tfor ( int m = 0; m < modelsCount; m++ )\r\n\t\t\t{\r\n\t\t\t\tif ( dominantMaterialId < _modelMaterialWeights[m].Length )\r\n\t\t\t\t{\r\n\t\t\t\t\tfloat w = _modelMaterialWeights[m][dominantMaterialId];\r\n\t\t\t\t\tif ( w > 0f )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tcurrentWeightSum += w;\r\n\t\t\t\t\t\tif ( roll <= currentWeightSum )\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tselectedModelIndex = m;\r\n\t\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tif ( selectedModelIndex == -1 ) continue;\r\n\t\t\tref var selectedModel = ref modelEntries[selectedModelIndex];\r\n\r\n\t\t\tif ( ReduceDensityOnTransitions )\r\n\t\t\t{\r\n\t\t\t\tfloat blendNorm = compactMat.BlendFactor * 0.003921569f;\r\n\t\t\t\tfloat purity = MathF.Abs( blendNorm - 0.5f ) * 2f;\r\n\t\t\t\tfloat spawnChance = MathF.Pow( purity, TransitionThinningIntensity );\r\n\t\t\t\tif ( random.NextSingle() > spawnChance ) continue;\r\n\t\t\t}\r\n\r\n\t\t\tif ( selectedModel.NoiseType != ScattererNoiseType.None )\r\n\t\t\t{\r\n\t\t\t\tvar noiseField = _modelNoiseFields[selectedModelIndex];\r\n\t\t\t\tfloat noiseVal = noiseField.Sample( worldX, worldY );\r\n\t\t\t\tif ( noiseVal < selectedModel.NoiseThreshold ) continue;\r\n\t\t\t}\r\n\r\n\t\t\tVector3 normal = Vector3.Up;\r\n\r\n\t\t\tif ( UseTerrainNormal || MaxSlopeAngle < 90f )\r\n\t\t\t{\r\n\t\t\t\tfloat hL = SampleHeightBilinear( u - textureEps, v );\r\n\t\t\t\tfloat hR = SampleHeightBilinear( u + textureEps, v );\r\n\t\t\t\tfloat hD = SampleHeightBilinear( u, v - textureEps );\r\n\t\t\t\tfloat hU = SampleHeightBilinear( u, v + textureEps );\r\n\t\t\t\tVector3 terrainTangentX =\r\n\t\t\t\t\tnew Vector3( 2.0f * textureEps * _terrainSize, 0f, (hR - hL) * _terrainHeight );\r\n\t\t\t\tVector3 terrainTangentY =\r\n\t\t\t\t\tnew Vector3( 0f, 2.0f * textureEps * _terrainSize, (hU - hD) * _terrainHeight );\r\n\t\t\t\tVector3 terrainNormalLocal = Vector3.Cross( terrainTangentX, terrainTangentY ).Normal;\r\n\t\t\t\tnormal = terrainTransform.Rotation * terrainNormalLocal;\r\n\t\t\t\tfloat slopeAngle = Vector3.GetAngle( Vector3.Up, normal );\r\n\t\t\t\tif ( slopeAngle > MaxSlopeAngle ) continue;\r\n\t\t\t}\r\n\r\n\t\t\tfloat z = SampleHeightBilinear( u, v ) * _terrainHeight;\r\n\t\t\tVector3 finalLocalPoint = new Vector3( terrainLocal.x, terrainLocal.y, z );\r\n\t\t\tVector3 worldTerrainPoint = terrainTransform.PointToWorld( finalLocalPoint );\r\n\r\n\t\t\tRotation randomYaw = RandomizeRotation\r\n\t\t\t\t? Rotation.FromYaw( random.NextSingle() * 360f )\r\n\t\t\t\t: Rotation.Identity;\r\n\r\n\t\t\tRotation finalRot = UseTerrainNormal\r\n\t\t\t\t? Rotation.FromToRotation( Vector3.Up, normal ) * randomYaw\r\n\t\t\t\t: randomYaw;\r\n\r\n\t\t\tfloat scale = selectedModel.RandomizeScale\r\n\t\t\t\t? (random.NextSingle() * (selectedModel.MaxScale - selectedModel.MinScale) + selectedModel.MinScale)\r\n\t\t\t\t: 1.0f;\r\n\r\n\t\t\tinstances.Add( new InstanceData\r\n\t\t\t{\r\n\t\t\t\tPosition = worldTerrainPoint, Rotation = finalRot, Scale = scale, ModelIndex = selectedModelIndex\r\n\t\t\t} );\r\n\r\n\t\t\tif ( dominantMaterialId < 32 )\r\n\t\t\t{\r\n\t\t\t\tplacedCountPerMaterial[dominantMaterialId]++;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn instances;\r\n\t}\r\n\r\n\t[MethodImpl( MethodImplOptions.AggressiveInlining )]\r\n\tprivate int HashCombine( int seed, int value )\r\n\t\t=> seed ^ (value + -1640531527 + (seed << 6) + (seed >> 2));\r\n\r\n\t[MethodImpl( MethodImplOptions.AggressiveInlining )]\r\n\tprivate bool IsObstruction( GameObject hitObject )\r\n\t{\r\n\t\tif ( hitObject == null ) return false;\r\n\t\tif ( _cachedHitObject == hitObject ) return _cachedIsObstruction;\r\n\t\t_cachedHitObject = hitObject;\r\n\t\t_cachedIsObstruction = hitObject.Components.Get<Terrain>() == null;\r\n\t\treturn _cachedIsObstruction;\r\n\t}\r\n\r\n\t[MethodImpl( MethodImplOptions.AggressiveInlining )]\r\n\tprivate float SampleHeightBilinear( float u, float v )\r\n\t{\r\n\t\tu = u.Clamp( 0f, 1f );\r\n\t\tv = v.Clamp( 0f, 1f );\r\n\t\tfloat gx = u * (_resolution - 1);\r\n\t\tfloat gy = v * (_resolution - 1);\r\n\t\tint ix = (int)gx;\r\n\t\tint iy = (int)gy;\r\n\t\tfloat fx = gx - ix;\r\n\t\tfloat fy = gy - iy;\r\n\t\tint ix1 = Math.Min( ix + 1, _resolution - 1 );\r\n\t\tint iy1 = Math.Min( iy + 1, _resolution - 1 );\r\n\t\tint row0 = iy * _resolution;\r\n\t\tint row1 = iy1 * _resolution;\r\n\t\tfloat h00 = _heightMapPixels[row0 + ix] * UshortMaxReciprocal;\r\n\t\tfloat h10 = _heightMapPixels[row0 + ix1] * UshortMaxReciprocal;\r\n\t\tfloat h01 = _heightMapPixels[row1 + ix] * UshortMaxReciprocal;\r\n\t\tfloat h11 = _heightMapPixels[row1 + ix1] * UshortMaxReciprocal;\r\n\t\tfloat h0 = h00 + (h10 - h00) * fx;\r\n\t\tfloat h1 = h01 + (h11 - h01) * fx;\r\n\t\treturn h0 + (h1 - h0) * fy;\r\n\t}\r\n}\r\n"
        }
    ]
}