Code/PerformantTerrainScatterer/PerformantTerrainScattererManager.Placement.cs
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;

		var instances = new List<InstanceData>( MaxInstancesPerChunk );
		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;
		float totalWeight = _totalWeight;

		for ( int i = 0; i < MaxInstancesPerChunk; i++ )
		{
			float localX = random.NextSingle() * ChunkSize;
			float localY = random.NextSingle() * ChunkSize;
			float worldX = chunkBaseX + localX;
			float worldY = chunkBaseY + localY;
			float roll = random.NextSingle() * totalWeight;
			float currentWeightSum = 0f;
			int selectedModelIndex = -1;

			for ( int m = 0; m < modelsCount; m++ )
			{
				ref var entry = ref modelEntries[m];
				if ( entry.Model == null ) continue;
				currentWeightSum += entry.Weight;
				if ( roll <= currentWeightSum )
				{
					selectedModelIndex = m;
					break;
				}
			}

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

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

			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;

			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;
			}

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

			if ( dominantMaterialId >= _allowedModelIndices[selectedModelIndex].Length ||
			   !_allowedModelIndices[selectedModelIndex][dominantMaterialId] ) 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
			} );
		}

		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;
	}


}