s&box Package Code Search

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

Showing code results for query: * (68 total matches found)
ali3nsystems.lib-atmokinetic_assets / Camera/CameraController.cs
Game library
namespace AtmokineticAssets;

public sealed partial class CameraController : BasePostProcess<CameraController>
{
	public Material material {get;set;} = Material.Load("materials/mat-camera_weather.vmat");
	public bool IsCameraSubmerged { get; private set; }
	public bool IsCameraFullySubmerged { get; private set; }
	public float TimeSinceCameraLastExitedWater;
	public bool WasCameraFullySubmerged;
	private bool _transitioningFromFullySubmerged;
	public WaterController WaterController;
	public CameraComponent CameraComponent;

	protected override void OnAwake()
	{
		CameraComponent = GameObject.GetComponent<CameraComponent>();
	}

	protected override void OnUpdate()
	{
		CheckWaterSubmersion();
		UpdateWaterDrips();
	}

	public override void Render()
	{
		if (WaterLineCameraComponent.IsValid())
		{
			RenderWaterLineTexture();
		}

		if (WaterController.IsValid())
		{
			UnderWaterShaderAttributes();
			// WaterDripsShaderAttributes();
			WaterLineShaderAttributes();
		}

		WaterDripsFadeShaderAttribute();
		Attributes.Set("Is Camera Submerged", (IsCameraSubmerged && !IsCameraFullySubmerged) ? 1f : 0f);
		Attributes.Set("Is Fully Submerged", IsCameraFullySubmerged ? 1f : 0f);

		Blit(BlitMode.WithBackbuffer(material, Stage.BeforePostProcess, 100), "Water Post Process");
	}
}
ali3nsystems.lib-atmokinetic_assets / Camera/CameraController.Weather.WaterLine.cs
Game library
namespace AtmokineticAssets;

public sealed partial class CameraController : BasePostProcess<CameraController>, Component.ExecuteInEditor
{
	public GameObject WaterLineCameraGameObject;
	public CameraComponent WaterLineCameraComponent;
	public Texture WaterLineTexture;

	private void CheckWaterSubmersion()
	{
		var corners = GetCameraNearPlaneCorners();
		if (corners == null)
		{
			IsCameraSubmerged = false;
			IsCameraFullySubmerged = false;
			if (WaterLineCameraGameObject.IsValid())
			{
				WaterController?.DestroyWaterLineMesh();
				DisableWaterLineRendering();
			}
			return;
		}

		// Cached by EnvironmentController on awake - no per-frame scene scan.
		var waterControllers = EnvironmentController.CachedWaterControllers;
		int maxSubmergedCount = 0;
		WaterController activeWater = null;

		foreach (var water in waterControllers)
		{
			if (!water.IsValid()) continue;

			int submergedCount = CountCameraSubmergedCorners(corners, water);
			if (submergedCount > maxSubmergedCount)
			{
				maxSubmergedCount = submergedCount;
				activeWater = water;
			}
		}

		bool WasCameraFullySubmerged = IsCameraFullySubmerged;
		IsCameraSubmerged = maxSubmergedCount >= 1;

		// Use offset check for fully Submerged (only applies when already in water)
		int fullySubmergedCount = activeWater != null ? CountCameraFullySubmergedCorners(corners, activeWater) : 0;
		bool isNowFullySubmerged = fullySubmergedCount == 4;
		bool isNowPartiallySubmerged = IsCameraSubmerged && !isNowFullySubmerged;

		// Handle water controller change
		if (activeWater != WaterController)
		{
			WaterController?.DestroyWaterLineMesh();
			WaterController = activeWater;
		}

		// Water line cube (only when partially Submerged)
		if (isNowPartiallySubmerged && !WaterLineCameraGameObject.IsValid())
		{
			WaterController?.CreateWaterLineMesh();
			ActivateWaterLineRendering();
		}

		else if (!isNowPartiallySubmerged && WaterLineCameraGameObject.IsValid())
		{
			WaterController?.DestroyWaterLineMesh();
			DisableWaterLineRendering();
		}

		// Delay IsCameraFullySubmerged off by one frame to prevent flash
		if (WasCameraFullySubmerged && isNowPartiallySubmerged)
		{
			if (_transitioningFromFullySubmerged)
			{
				IsCameraFullySubmerged = false;
				_transitioningFromFullySubmerged = false;
			}
			else
			{
				IsCameraFullySubmerged = true;
				_transitioningFromFullySubmerged = true;
			}
		}
		else
		{
			IsCameraFullySubmerged = isNowFullySubmerged;
			_transitioningFromFullySubmerged = false;
		}
	}

	public void WaterLineShaderAttributes()
	{
		Attributes.Set("Water Line Size", WaterController.WaterLineSize);
		Attributes.Set("Water Line Blur", WaterController.WaterLineBlur);
		Attributes.Set("Water Line Color", WaterController.WaterLineColor);
	}

    private void ActivateWaterLineRendering()
	{
		CameraComponent.RenderExcludeTags.Add("water_line_mesh");

		WaterLineCameraGameObject = new GameObject { Name = "Water Line Camera", Parent = GameObject };
		// Local-only render helper - never include it in network snapshots.
		WaterLineCameraGameObject.Flags |= GameObjectFlags.NotNetworked;
		WaterLineCameraGameObject.NetworkMode = NetworkMode.Never;
		WaterLineCameraComponent = WaterLineCameraGameObject.AddComponent<CameraComponent>();
		WaterLineCameraComponent.BackgroundColor = Color.Black;
		WaterLineCameraComponent.IsMainCamera = false;
		WaterLineCameraComponent.FovAxis = CameraComponent.FovAxis;
		WaterLineCameraComponent.FieldOfView = CameraComponent.FieldOfView;
		WaterLineCameraComponent.ZNear = CameraComponent.ZNear;
		WaterLineCameraComponent.ZFar = CameraComponent.ZFar;
		WaterLineCameraComponent.Priority = CameraComponent.Priority + 1;
		WaterLineCameraComponent.Orthographic = CameraComponent.Orthographic;
		WaterLineCameraComponent.OrthographicHeight = CameraComponent.OrthographicHeight;
		WaterLineCameraComponent.TargetEye = CameraComponent.TargetEye;
		WaterLineCameraComponent.Viewport = CameraComponent.Viewport;
		WaterLineCameraComponent.RenderTags.Add("water_line_mesh");
		WaterLineCameraComponent.RenderExcludeTags.RemoveAll();
		WaterLineCameraComponent.EnablePostProcessing = false;
	}

	private void RenderWaterLineTexture()
	{
		WaterLineTexture = Texture.CreateRenderTarget("WaterLineRenderTexture", ImageFormat.A8, Screen.Size, WaterLineTexture);
		WaterLineCameraComponent.RenderTarget = WaterLineTexture;
		Attributes.Set("WaterLineTexture", WaterLineTexture);
	}

	private void DisableWaterLineRendering()
	{
		WaterLineCameraGameObject.Destroy();
		WaterLineCameraGameObject = null;
		WaterLineCameraComponent = null;

		WaterLineTexture.Dispose();
		WaterLineTexture = null;

		CameraComponent.RenderExcludeTags.Remove("water_line_mesh");
	}
}
ali3nsystems.lib-atmokinetic_assets / Code/Assembly.cs
Game library
global using Sandbox;
global using Sandbox.Rendering;
global using System;
global using System.Collections.Generic;
global using System.Runtime.InteropServices;
global using System.Text.RegularExpressions;
global using System.Linq;
ali3nsystems.lib-atmokinetic_assets / Code/Camera/CameraController.Weather.UnderWater.cs
Game library
namespace AtmokineticAssets;

public sealed partial class CameraController : BasePostProcess<CameraController>
{
    public void UnderWaterShaderAttributes()
    {
        Attributes.Set("Under Water Blur", WaterController.UnderWaterBlur);
        Attributes.Set("Under Water Color", WaterController.UnderWaterColor);
    }	
}
ali3nsystems.lib-atmokinetic_assets / Code/Graphics/GraphicsSettings.TerrainSettings.cs
Game library
namespace AtmokineticAssets;

public sealed partial class GraphicsSettings : Component, Component.ExecuteInEditor
{
    //Terrain Settings
    public enum TerrainRenderingModeEnumeration
    {
        [Title("Parallax Occlusion Mapping")]
        ParallaxOcclusionMapping,
        
        [Title("Bump Offset")]
        BumpOffset,
       
        Standard
    }

    //[Property, Feature("Terrain"), Title("Rendering")]
    public TerrainRenderingModeEnumeration TerrainRenderingMode { get; set; } = TerrainRenderingModeEnumeration.Standard;

    public enum TerrainRenderingParallaxOcclusionMappingQualityEnumeration
    {
        Ultra,
        High,
        Medium,
        Low
    }

    //[Property, Feature("Terrain"), Title("Quality")] 
    [ShowIf(nameof(TerrainRenderingMode), TerrainRenderingModeEnumeration.ParallaxOcclusionMapping)]
    public TerrainRenderingParallaxOcclusionMappingQualityEnumeration TerrainRenderingParallaxOcclusionMapping { get; set; } = TerrainRenderingParallaxOcclusionMappingQualityEnumeration.Medium;

    public enum TerrainRenderingBumpOffsetQualityEnumeration
    {
        Ultra,
        High,
        Medium,
        Low
    }
   
    //[Property, Feature("Terrain"), Title("Quality")] 
    [ShowIf(nameof(TerrainRenderingMode), TerrainRenderingModeEnumeration.BumpOffset)]
    public TerrainRenderingParallaxOcclusionMappingQualityEnumeration TerrainRenderingBumpOffset { get; set; } = TerrainRenderingParallaxOcclusionMappingQualityEnumeration.Medium;


    // public void ChangeTerrainSettings()
    // {
    //             // Terrain rendering mode: 0 = POM, 1 = Bump Offset, 2 = Standard
    //     int terrainRenderingMode = TerrainRenderingMode switch
    //     {
    //         TerrainRenderingModeEnumeration.ParallaxOcclusionMapping => 0,
    //         TerrainRenderingModeEnumeration.BumpOffset => 1,
    //         TerrainRenderingModeEnumeration.Standard => 2,
    //         _ => 1
    //     };

    //     // Quality: Ultra = 1, High = 2, Medium = 4, Low = 8
    //     float quality;
    //     switch ( TerrainRenderingMode )
    //     {
    //         case TerrainRenderingModeEnumeration.ParallaxOcclusionMapping:
    //             quality = TerrainRenderingParallaxOcclusionMapping switch
    //             {
    //                 TerrainRenderingParallaxOcclusionMappingQualityEnumeration.Ultra => 1f,
    //                 TerrainRenderingParallaxOcclusionMappingQualityEnumeration.High => 2f,
    //                 TerrainRenderingParallaxOcclusionMappingQualityEnumeration.Medium => 4f,
    //                 TerrainRenderingParallaxOcclusionMappingQualityEnumeration.Low => 8f,
    //                 _ => 4f
    //             };
    //             break;

    //         case TerrainRenderingModeEnumeration.BumpOffset:
    //             quality = TerrainRenderingBumpOffset switch
    //             {
    //                 TerrainRenderingParallaxOcclusionMappingQualityEnumeration.Ultra => 1f,
    //                 TerrainRenderingParallaxOcclusionMappingQualityEnumeration.High => 2f,
    //                 TerrainRenderingParallaxOcclusionMappingQualityEnumeration.Medium => 4f,
    //                 TerrainRenderingParallaxOcclusionMappingQualityEnumeration.Low => 8f,
    //                 _ => 4f
    //             };
    //             break;

    //         default:
    //             quality = 4f;
    //             break;
    //     }

    //     // Enumerate live — objects spawned after startup (e.g. baked water tiles) must be included.
    //     foreach ( var sceneObject in Scene.SceneWorld.SceneObjects )
    //     {
    //         if ( sceneObject is null || !sceneObject.IsValid() ) continue;
    //         sceneObject.Attributes.Set( "D_Rendering", terrainRenderingMode );
    //         sceneObject.Attributes.Set( "Quality", quality );
    //     }
    // }
}
ali3nsystems.lib-atmokinetic_assets / Player/CharacterControllerUtilities.DisableSelfCollision.cs
Game library
using Sandbox.Physics;

namespace AtmokineticAssets;

[Title( "Atmokinetic Assets - Character Controller Utilities" )]
[Category( "Atmokinetic Assets" )]
[Icon( "badge" )]


public sealed partial class CharacterControllerUtilities : Component, Component.INetworkSpawn
{
    /// <summary>
    /// THIS IS PRIMARILY USED FOR THE MODELPHYISICS TO GENERATE WATER SIMULATIONS/RIPPLES
    /// WITHOUT COLLIDING WITH THE CHARACTER CONTROLLER'S BOX/CAPSULE COLLIDER. 
    /// IT USES THE STEAMID AS AN IDENTIFIER.
    /// </summary>

    private string _appliedTag;
    private ModelPhysics _modelPhysics;
    private int _lastTaggedBodyCount = -1;

    public void OnNetworkSpawn( Connection owner )
    {
        EnsurePhysics();
        ApplyTag( owner );
    }

    protected override void OnStart()
    {
        EnsurePhysics();

        // Fallback for non-networked scenes: tag with the local connection
        ApplyTag( Network.Owner ?? Connection.Local );
    }

    // Create a ModelPhysics from the player's SkinnedModelRenderer if one is
    // missing, so the collision bodies this component tags actually exist.
    private void EnsurePhysics()
    {
        if ( GameObject.GetComponentInChildren<ModelPhysics>( true ).IsValid() )
            return;

        var renderer = GameObject.GetComponentInChildren<SkinnedModelRenderer>( true );
        if ( !renderer.IsValid() || renderer.Model is null )
            return;

        var physics = renderer.GameObject.AddComponent<ModelPhysics>();
        physics.Renderer = renderer;
        physics.Model = renderer.Model;
        physics.MotionEnabled = false;
    }

    protected override void OnUpdate()
    {
        // ModelPhysics creates its ragdoll bone objects lazily (and rebuilds them on
        // model reload). Instead of walking every child collider each tick, watch the
        // body count - it only changes when the ragdoll is (re)built.
        if ( string.IsNullOrEmpty( _appliedTag ) )
            return;

        if ( !_modelPhysics.IsValid() )
            _modelPhysics = GameObject.GetComponentInChildren<ModelPhysics>( true );

        int bodyCount = _modelPhysics.IsValid() ? _modelPhysics.Bodies.Count : 0;
        if ( bodyCount == _lastTaggedBodyCount )
            return;

        _lastTaggedBodyCount = bodyCount;
        TagColliders( _appliedTag );
    }

    private void ApplyTag( Connection connection )
    {
        if ( connection is null )
            return;

        var tag = connection.SteamId.ToString();

        if ( _appliedTag == tag )
            return;

        // Clean up a previously applied id (e.g. after ownership transfer)
        if ( !string.IsNullOrEmpty( _appliedTag ) )
            GameObject.Tags.Remove( _appliedTag );

        // The tag itself replicates over the network (it's GameObject state), so a
        // joining client can see it already applied from the snapshot - but the
        // collision-ignore rule lives in this machine's own PhysicsWorld, which is
        // NOT networked. So only skip re-adding the tag; always (re)register the
        // rule locally, or clients would collide with their own ragdoll forever.
        if ( !GameObject.Tags.Has( tag ) )
            GameObject.Tags.Add( tag );

        IgnoreSelfCollision( tag );
        TagColliders( tag );

        _appliedTag = tag;
    }

    // Add a rule so any two shapes both carrying this exact Steam ID tag don't
    // collide. Only this player's own colliders carry it, so its ragdoll ignores
    // its own body while still colliding with other players (whose tag differs).
    private void IgnoreSelfCollision( string tag )
    {
        var rules = Scene.PhysicsWorld?.CollisionRules;
        if ( rules is null )
            return;

        rules.Pairs[new CollisionRules.Pair( tag, tag )] = CollisionRules.Result.Ignore;
    }

    // Stamp the Steam ID tag onto every collider under the player. Shapes take
    // their tags from their GameObject, and ragdoll bone objects don't inherit
    // the root's tags, so each one has to be tagged directly.
    private void TagColliders( string tag )
    {
        foreach ( var collider in GameObject.GetComponentsInChildren<Collider>( true ) )
        {
            if ( !collider.GameObject.Tags.Has( tag ) )
                collider.GameObject.Tags.Add( tag );
        }
    }

}
ali3nsystems.lib-atmokinetic_assets / Water/Modes/Tiles/WaterController.WaterTiles.cs
Game library
namespace AtmokineticAssets;

public sealed partial class WaterController: Component, Component.ITriggerListener
{
	
	
	[Property, Title("Tile Model"), Feature("General"), Group("Properties"), ShowIf( nameof(WaterMode), WaterModeEnumeration.Tiles )]
	public Model WaterTileModel { get; set; } = Model.Load("models/mdl-water_tile.vmdl");
	
	[Property, Title("Tile Size (cm)"), Feature("General"), Group("Properties"), ShowIf( nameof(WaterMode), WaterModeEnumeration.Tiles )]
	public float WaterTileSize
	{
		get;
		set
		{
			field = ResizeWaterTileBounds( field, value );
		}
	} = 500f;
	
	[Property, Title("Bounds Maximum (cm)"), Feature("General"), Group("Properties"), ShowIf( nameof(WaterMode), WaterModeEnumeration.Tiles )]
	public Vector3 WaterTileBoundsMaximum { get; set; } = new( 500, 500, 500 );

	[Property, Title("Bounds Minimum (cm)"), Feature("General"), Group("Properties"), ShowIf( nameof(WaterMode), WaterModeEnumeration.Tiles )]
	public Vector3 WaterTileBoundsMinimum { get; set; } = new( -500, -500, -500 );

	[Button("Generate Water Tiles"), Feature("General"), Group("Properties"), ShowIf( nameof(WaterMode), WaterModeEnumeration.Tiles )]
	public void GenerateWaterTiles()
	{
		Bake();
		WaterSurfaceAttributes();
	}

	[Property, Title("Enable Gizmo"),Feature( "General" ), ShowIf( nameof(WaterMode), WaterModeEnumeration.Tiles)]
	public bool EnableWaterTileGizmo { get; set; } = true;
	private readonly Dictionary<(int x, int y), (GameObject go, ModelRenderer renderer)> _tiles = [];
	private Vector2 _lastSnappedPosition = new( float.MinValue, float.MinValue );
	private BoxCollider _waterCollider;
	private bool _waterLineActive;
	private readonly Dictionary<(int x, int y), GameObject> _waterLineChildren = [];

	// True once the component has started — gates editor-only bounds rescaling so it
	// never fires during scene deserialization (which would clobber the saved bounds).
	private bool _tilesStarted;

	public float SubmersionOffset => -(WaterRipplesDisplacementA + WaterRipplesDisplacementB);

	// World-adjusted snapped bounds (inches). Internal counterparts to the public
	// WaterTileBounds* props, which must NEVER be written by code.
	private Vector3 _waterTileBoundsMinimum;
	private Vector3 _waterTileBoundsMaximum;

	// Integer tile-grid indices, computed once in CalculateBounds (half-open range).
	private int _tileMinX, _tileMaxX, _tileMinY, _tileMaxY;

	// Cached bounds in world space (inches) — derived from the integer indices above.
	private Vector2 _boundsMin;
	private Vector2 _boundsMax;
	private float _waterZ;

	// Tile size in cm (all meshes are same size)

	public IEnumerable<ModelRenderer> GetTileRenderers()
	{
		foreach ( var kvp in _tiles )
		{
			if ( kvp.Value.renderer.IsValid() )
				yield return kvp.Value.renderer;
		}
	}

	public void Bake()
	{
		CalculateRingLayout();
		DestroyAllTiles();
		CalculateBounds();
		CreateWaterCollider();
		BakeTiles();
	}

	public float GetWaterSurfaceZ() => _waterZ;
	public float GetWaterSurfaceZWithOffset() => _waterZ + SubmersionOffset;

	public BBox GetWorldBounds()
	{
		float minZ = WorldPosition.z + UnitConversion.CentimetersToInches( WaterTileBoundsMinimum.z );
		return new BBox(
			new Vector3( _boundsMin.x, _boundsMin.y, minZ ),
			new Vector3( _boundsMax.x, _boundsMax.y, _waterZ ) );
	}

	// True when the whole water bounds are outside the camera frustum, i.e. every tile is
	// culled. One frustum-vs-BBox test per frame, independent of tile count.
	// (SceneObject.RenderingEnabled can't be used — it's a toggle you set, not a per-frame
	// cull result, and the engine doesn't expose "was culled last frame" publicly. Frustum
	// is the only reliable option; it misses occlusion, which isn't cheaply queryable.)
	public bool AreAllTilesCulled()
	{
		var camera = Scene.Camera;
		if ( !camera.IsValid() )
			return true;

		var frustum = camera.GetFrustum( new Rect( 0, 0, Screen.Width, Screen.Height ) );
		return !frustum.IsInside( GetWorldBounds(), partially: true );
	}

	public bool IsPointInWaterVolume( Vector3 point )
	{
		var b = GetWorldBounds();
		return point.x >= b.Mins.x && point.x <= b.Maxs.x &&
			   point.y >= b.Mins.y && point.y <= b.Maxs.y &&
			   point.z < GetWaterSurfaceZ() + ripples.Count && point.z >= b.Mins.z;
	}

	public bool IsPointFullySubmerged( Vector3 point )
	{
		var b = GetWorldBounds();
		return point.x >= b.Mins.x && point.x <= b.Maxs.x &&
			   point.y >= b.Mins.y && point.y <= b.Maxs.y &&
			   point.z < GetWaterSurfaceZWithOffset() && point.z >= b.Mins.z;
	}

	// Body group layout convention on the "subdivision" body group:
	//   0        : water-line variant of ring 0 (rendered only by the water-line camera)
	//   1        : ring 0 (center, 2x2 quadrants)
	//   2..n-2   : an end/corner pair per ring (subdivided_N_end, subdivided_N_corner)
	//   n-1      : the last subdivision (far filler, no variants)
	// MaxRings and the far index are derived from the model in CalculateRingLayout.
	private int MaxRings { get; set; } = 4;
	private int FarBodyGroupIndex { get; set; } = 8;

	// Derive the ring count from the model's "subdivision" body group, so adding or
	// removing ring meshes in the model needs no code change.
	private void CalculateRingLayout()
	{
		var subdivisionPart = WaterTileModel?.Parts.All
			.FirstOrDefault( p => p.Name == "subdivision" );

		// Minimum viable layout: waterline + ring 0 + one end/corner pair + far.
		if ( subdivisionPart is null || subdivisionPart.Choices.Count < 5 )
			return;

		int choiceCount = subdivisionPart.Choices.Count;

		if ( ( choiceCount - 3 ) % 2 != 0 )
			Log.Warning( $"Water tile model '{WaterTileModel.ResourceName}' has {choiceCount} subdivision choices - expected waterline + ring0 + end/corner pairs + far. Ring layout may be wrong." );

		FarBodyGroupIndex = choiceCount - 1;
		MaxRings = ( choiceCount - 3 ) / 2 + 1;
	}

	private enum TileType
	{
		End,      // Outline side
		Corner    // Outline corner
	}

	private int GetBodyGroupForRing( int ringLevel, TileType tileType )
	{
		if ( ringLevel == 0 )
			return 1;
		if ( ringLevel >= MaxRings )
			return FarBodyGroupIndex;

		int baseIndex = ( ringLevel - 1 ) * 2 + 2;
		return tileType == TileType.Corner ? baseIndex + 1 : baseIndex;
	}

	private void UpdateRings()
	{
		if ( _tiles.Count == 0 )
			return;

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

		// Half-tile bias shifts ring transitions to tile centers instead of tile edges.
		// Snap the camera in box-local space (relative to the GameObject origin) to match the tile layout.
		float tileSizeInches = UnitConversion.CentimetersToInches( WaterTileSize );
		float halfTile = tileSizeInches * 0.5f;
		float localCamX = camera.WorldPosition.x - WorldPosition.x;
		float localCamY = camera.WorldPosition.y - WorldPosition.y;
		float snappedX = MathF.Floor( ( localCamX - halfTile ) / tileSizeInches ) * tileSizeInches;
		float snappedY = MathF.Floor( ( localCamY - halfTile ) / tileSizeInches ) * tileSizeInches;
		Vector2 snappedPos = new( snappedX, snappedY );

		if ( snappedPos != _lastSnappedPosition )
		{
			_lastSnappedPosition = snappedPos;
			UpdateTileBodyGroups( snappedPos );
			SyncWaterLineChildren();
		}
	}

	private void DestroyAllTiles()
	{
		foreach ( var kvp in _tiles )
		{
			if ( kvp.Value.go.IsValid() )
				kvp.Value.go.Destroy();
		}
		_tiles.Clear();
		MarkRippleRenderersDirty();
	}

	public void CreateWaterLineMesh()
	{
		if ( _waterLineActive ) return;
		_waterLineActive = true;
		// If tiles are already live, sync immediately; otherwise OnUpdate will handle it on next snap.
		if ( _tiles.Count > 0 )
			SyncWaterLineChildren();
	}

	public void DestroyWaterLineMesh()
	{
		_waterLineActive = false;
		foreach ( var kvp in _waterLineChildren )
		{
			if ( kvp.Value.IsValid() )
				kvp.Value.Destroy();
		}
		_waterLineChildren.Clear();
		MarkRippleRenderersDirty();
	}

	private void SyncWaterLineChildren()
	{
		if ( !_waterLineActive || WaterTileModel == null )
			return;

		var ringZeroKeys = new HashSet<(int x, int y)>();
		foreach ( var kvp in _tiles )
		{
			if ( kvp.Value.renderer.IsValid() && kvp.Value.renderer.GetBodyGroup( "subdivision" ) == 1 )
				ringZeroKeys.Add( kvp.Key );
		}

		var stale = new List<(int x, int y)>();
		foreach ( var kvp in _waterLineChildren )
		{
			if ( ringZeroKeys.Contains( kvp.Key ) )
				continue;
			if ( kvp.Value.IsValid() )
				kvp.Value.Destroy();
			stale.Add( kvp.Key );
		}
		foreach ( var k in stale )
			_waterLineChildren.Remove( k );

		if ( stale.Count > 0 )
			MarkRippleRenderersDirty();

		foreach ( var key in ringZeroKeys )
		{
			if ( _waterLineChildren.ContainsKey( key ) )
				continue;
			if ( !_tiles.TryGetValue( key, out var tile ) || !tile.go.IsValid() )
				continue;

			var childGo = new GameObject( true, "waterline" ) { Parent = tile.go };
			// Local-only render mesh - never include it in network snapshots.
			childGo.Flags |= GameObjectFlags.NotNetworked;
			childGo.NetworkMode = NetworkMode.Never;
			childGo.Tags.Add( "water_line_mesh" );

			var r = childGo.Components.Create<ModelRenderer>();
			r.Model = WaterTileModel;

			r.RenderType = ModelRenderer.ShadowRenderType.Off;
			r.SceneObject.Attributes.Set( "BoundsMin", _boundsMin );
			r.SceneObject.Attributes.Set( "BoundsMax", _boundsMax );
			
			r.SceneObject.Attributes.Set( "WaterRipplesTextureA", WaterRipplesTextureA );
			r.SceneObject.Attributes.Set( "Water Ripples Scale A", WaterRipplesScaleA );
			r.SceneObject.Attributes.Set( "Water Ripples Rotation A", WaterRipplesRotationA );
			r.SceneObject.Attributes.Set( "Water Ripples Speed A", WaterRipplesSpeedA );
			r.SceneObject.Attributes.Set( "Water Ripples Displacement A", WaterRipplesDisplacementA );

			r.SceneObject.Attributes.Set( "WaterRipplesTextureB", WaterRipplesTextureB );
			r.SceneObject.Attributes.Set( "Water Ripples Scale B", WaterRipplesScaleB );
			r.SceneObject.Attributes.Set( "Water Ripples Rotation B", WaterRipplesRotationB );
			r.SceneObject.Attributes.Set( "Water Ripples Speed B", WaterRipplesSpeedB );
			r.SceneObject.Attributes.Set( "Water Ripples Displacement B", WaterRipplesDisplacementB );


			r.Attributes.SetCombo( "D_TRANSLUCENT", 0 );
			r.Attributes.SetCombo( "D_Enable_Water_Line", 1 );
			r.SetBodyGroup( "subdivision", 0 );

			_waterLineChildren[key] = childGo;
			MarkRippleRenderersDirty();
		}
	}

	public void OnTriggerEnter( Collider other )
	{
		if ( !other.GameObject.Tags.Has( "particles" ) )
			return;

		other.GameObject.Destroy();
	}

	public void OnTriggerExit( Collider other )
	{
	}

	private void CreateWaterCollider()
	{
		// Only create the trigger collider at runtime, not while editing the scene.
		if ( Scene.IsEditor )
			return;

		// Use the snapped tile region so the trigger matches the spawned tiles.
		// _waterTileBounds* are world-space; convert to local for the (parented) collider.
		Vector3 localMin = _waterTileBoundsMinimum - WorldPosition;
		Vector3 localMax = _waterTileBoundsMaximum - WorldPosition;

		Vector3 center = ( localMin + localMax ) * 0.5f;
		Vector3 size = localMax - localMin;

		_waterCollider ??= Components.Get<BoxCollider>( FindMode.InSelf ) ?? Components.Create<BoxCollider>();
		_waterCollider.Flags = ComponentFlags.Hidden;
		_waterCollider.Center = center;
		_waterCollider.Scale = size;
		_waterCollider.IsTrigger = true;
		_waterCollider.Static = true;
	}

	private void CalculateBounds()
	{
		var minInches = UnitConversion.CentimetersToInches( WaterTileBoundsMinimum );
		var maxInches = UnitConversion.CentimetersToInches( WaterTileBoundsMaximum );

		float tileSizeInches = UnitConversion.CentimetersToInches( WaterTileSize );

		// Snap the extent to the tile grid in LOCAL space (relative to the GameObject origin).
		// Tiles are anchored to the box, so they fill it exactly and follow the GameObject smoothly.
		_tileMinX = (int)MathF.Floor( MathF.Round( minInches.x / tileSizeInches, 4 ) );
		_tileMinY = (int)MathF.Floor( MathF.Round( minInches.y / tileSizeInches, 4 ) );
		_tileMaxX = (int)MathF.Ceiling( MathF.Round( maxInches.x / tileSizeInches, 4 ) );
		_tileMaxY = (int)MathF.Ceiling( MathF.Round( maxInches.y / tileSizeInches, 4 ) );

		// Local-space snapped corners.
		float minLocalX = _tileMinX * tileSizeInches;
		float minLocalY = _tileMinY * tileSizeInches;
		float maxLocalX = _tileMaxX * tileSizeInches;
		float maxLocalY = _tileMaxY * tileSizeInches;

		// World-space bounds (for collider-independent consumers, shader clipping, point queries).
		_waterTileBoundsMinimum = new Vector3( WorldPosition.x + minLocalX, WorldPosition.y + minLocalY, WorldPosition.z + minInches.z );
		_waterTileBoundsMaximum = new Vector3( WorldPosition.x + maxLocalX, WorldPosition.y + maxLocalY, WorldPosition.z + maxInches.z );

		_boundsMin = new Vector2( _waterTileBoundsMinimum.x, _waterTileBoundsMinimum.y );
		_boundsMax = new Vector2( _waterTileBoundsMaximum.x, _waterTileBoundsMaximum.y );
		_waterZ = _waterTileBoundsMaximum.z;
	}

	private void DiscoverBakedTiles()
	{
		CalculateRingLayout();

		// Rediscovered renderers need the surface attributes even if no property changed.
		_surfaceAttributesDirty = true;
		MarkRippleRenderersDirty();


		foreach ( var child in GameObject.Children.ToList() )
		{
			if ( !child.Name.StartsWith( "tile_" ) )
				continue;

			var renderer = child.Components.Get<ModelRenderer>();
			if ( renderer == null )
				continue;

			renderer.SceneObject.Attributes.Set( "BoundsMin", _boundsMin );
			renderer.SceneObject.Attributes.Set( "BoundsMax", _boundsMax );
			renderer.Attributes.SetCombo( "D_TRANSLUCENT", 1 );
			renderer.Attributes.SetCombo( "D_Enable_Water_Line", 0 );

			// Parse tileX, tileY from name "tile_X_Y"
			var parts = child.Name.Split( '_' );
			if ( parts.Length == 3 && int.TryParse( parts[1], out int tileX ) && int.TryParse( parts[2], out int tileY ) )
			{
				_tiles[(tileX, tileY)] = (child, renderer);
			}
		}
	}

	private void BakeTiles()
	{
		if ( WaterTileModel == null )
			return;

		float tileSizeInches = UnitConversion.CentimetersToInches( WaterTileSize );
		float halfTile = tileSizeInches * 0.5f;

		for ( int tileX = _tileMinX; tileX < _tileMaxX; tileX++ )
		{
			for ( int tileY = _tileMinY; tileY < _tileMaxY; tileY++ )
			{
				// Tile indices are local-grid; anchor positions to the GameObject origin.
				float worldX = WorldPosition.x + tileX * tileSizeInches;
				float worldY = WorldPosition.y + tileY * tileSizeInches;

				Vector3 worldPosition = new Vector3( worldX + halfTile, worldY + halfTile, _waterZ );
				SpawnTile( worldPosition, tileX, tileY );
			}
		}
	}

	private void UpdateTileBodyGroups( Vector2 snappedPos )
	{
		float tileSizeInches = UnitConversion.CentimetersToInches( WaterTileSize );

		// First pass: calculate ring levels and find outermost ring
		var tileData = new Dictionary<(int x, int y), (int ringLevel, int gridX, int gridY)>();
		int outermostRenderedRing = 0;

		foreach ( var kvp in _tiles )
		{
			int tileX = kvp.Key.x;
			int tileY = kvp.Key.y;

			// Box-local tile position (indices are local-grid; snappedPos is box-local).
			float localX = tileX * tileSizeInches;
			float localY = tileY * tileSizeInches;

			int gridX = (int)MathF.Round( ( localX - snappedPos.x ) / tileSizeInches );
			int gridY = (int)MathF.Round( ( localY - snappedPos.y ) / tileSizeInches );

			int ringLevel = Math.Max( GetRingForCoord( gridX ), GetRingForCoord( gridY ) );
			if ( ringLevel > MaxRings )
				ringLevel = MaxRings;

			if ( ringLevel > outermostRenderedRing )
				outermostRenderedRing = ringLevel;

			tileData[(tileX, tileY)] = (ringLevel, gridX, gridY);
		}

		// Second pass: update body groups
		foreach ( var kvp in _tiles )
		{
			var renderer = kvp.Value.renderer;
			if ( !renderer.IsValid() )
				continue;

			var (ringLevel, gridX, gridY) = tileData[kvp.Key];

			TileType tileType = TileType.End;
			float rotation = 0f;

			if ( ringLevel == 0 )
			{
				if ( gridX == 0 && gridY == 0 ) rotation = 0f;
				else if ( gridX == 1 && gridY == 0 ) rotation = 90f;
				else if ( gridX == 0 && gridY == 1 ) rotation = 270f;
				else if ( gridX == 1 && gridY == 1 ) rotation = 180f;
			}
			else if ( ringLevel < MaxRings )
			{
				// Rings are 1-tile-thick outlines - every tile is an End or a Corner.
				if ( IsCornerTile( gridX, gridY, ringLevel ) )
				{
					tileType = TileType.Corner;
					rotation = GetCornerRotation( gridX, gridY, ringLevel );
				}
				else
				{
					rotation = GetEdgeRotation( gridX, gridY, ringLevel );
				}
			}

			renderer.SetBodyGroup( "subdivision", GetBodyGroupForRing( ringLevel, tileType ) );
			kvp.Value.go.WorldRotation = Rotation.FromYaw( -90f + rotation );
		}
	}

	private int GetRingForCoord( int gridCoord )
	{
		// Center (ring 0): coords 0, 1 (the 2x2 quadrant block)
		// Ring N (1 tile thick outline): coord -N on the negative side, N+1 on the positive side
		//   Ring 1: -1, 2
		//   Ring 2: -2, 3
		//   Ring 3: -3, 4
		if ( gridCoord >= 0 && gridCoord <= 1 )
			return 0;

		int ring = gridCoord < 0 ? -gridCoord : gridCoord - 1;

		if ( ring > MaxRings )
			return MaxRings + 1;

		return ring;
	}

	private bool IsCornerTile( int gridX, int gridY, int ringLevel )
	{
		int outerMin = -ringLevel;
		int outerMax = ringLevel + 1;

		bool atCornerX = ( gridX == outerMin || gridX == outerMax );
		bool atCornerY = ( gridY == outerMin || gridY == outerMax );

		return atCornerX && atCornerY;
	}

	private float GetCornerRotation( int gridX, int gridY, int ringLevel )
	{
		int outerMin = -ringLevel;
		int outerMax = ringLevel + 1;

		if ( gridX == outerMin && gridY == outerMax ) return 270f;
		if ( gridX == outerMax && gridY == outerMax ) return 180f;
		if ( gridX == outerMax && gridY == outerMin ) return 90f;
		if ( gridX == outerMin && gridY == outerMin ) return 0f;

		return 0f;
	}

	private float GetEdgeRotation( int gridX, int gridY, int ringLevel )
	{
		int outerMin = -ringLevel;
		int outerMax = ringLevel + 1;

		if ( gridY == outerMax ) return 180f;
		if ( gridX == outerMax ) return 90f;
		if ( gridY == outerMin ) return 0f;
		if ( gridX == outerMin ) return 270f;

		return 0f;
	}

	private void SpawnTile( Vector3 worldPosition, int tileX, int tileY )
	{
		var tileGo = new GameObject( true, $"tile_{tileX}_{tileY}" );
		tileGo.Parent = GameObject;
		tileGo.Tags.Add( "reflection" ); // excluded from the reflection camera so water doesn't reflect itself
		tileGo.WorldPosition = worldPosition;
		tileGo.LocalScale = Vector3.One * ( WaterTileSize / 100f );

		var renderer = tileGo.Components.Create<ModelRenderer>();
		renderer.Model = WaterTileModel;

		renderer.SceneObject.Attributes.Set( "BoundsMin", _boundsMin );
		renderer.SceneObject.Attributes.Set( "BoundsMax", _boundsMax );
		renderer.Attributes.SetCombo( "D_TRANSLUCENT", 1 );
		renderer.Attributes.SetCombo( "D_Enable_Water_Line", 0 );

		renderer.SetBodyGroup( "subdivision", FarBodyGroupIndex );

		_tiles[(tileX, tileY)] = (tileGo, renderer);

		// New renderer needs the surface attributes, ripple attributes and the
		// reflection-mode combo even if nothing changed.
		_surfaceAttributesDirty = true;
		MarkRippleRenderersDirty();
		_lastWaterReflectionPlanarRendering = null;
	}
}
ali3nsystems.lib-atmokinetic_assets / Water/Modes/WorkInProgress/Area/WaterController.WaterArea.cs
Game library
namespace AtmokineticAssets;

// Soon TM
// This will be used for very large body of waters like oceans, lakes, etc...
public sealed partial class WaterController: Component
{
	// [Property, Title("Area Model"), Feature("General"), Group("Properties"), ShowIf( nameof(WaterMode), WaterModeEnumeration.Area )]
	// public Model WaterAreaModel { get; set; }

	// [Property, Title("Bounds Minimum (cm)"), Feature("General"), Group("Properties"), ShowIf( nameof(WaterMode), WaterModeEnumeration.Area )]
	// public Vector3 WaterAreaBoundsMinimum = new( -100, -100, -100 );

	// [Property, Title("Bounds Maximum (cm)"), Feature("General"), Group("Properties"), ShowIf( nameof(WaterMode), WaterModeEnumeration.Area )]
	// public Vector3 WaterAreaBoundsMaximum = new( 100, 100, 0 );

	// [Button("Generate Water Area"), Feature("General"), Group("Properties"), ShowIf( nameof(WaterMode), WaterModeEnumeration.Area )]
	// public void GenerateWaterArea()
	// {

	// }
}
ali3nsystems.lib-atmokinetic_assets / Water/Reflection/Wip/WaterController.Reflection.Cubemap.cs
Game library
// namespace AtmokineticAssets;

// public sealed partial class WaterController : Component, Component.ExecuteInEditor
// {
// 	public bool ShowWaterReflectionCubemapSettings =>
// 	WaterReflectionMode == WaterReflectionModeEnumeration.Dynamic
// 	|| ( WaterReflectionMode == WaterReflectionModeEnumeration.Static
// 	&& WaterReflectionModeStaticMode == WaterReflectionModeStaticModeEnumeration.Cubemap );
// 	public bool ShowWaterReflectionCubemapResolutionDynamic => ShowWaterReflectionCubemapSettings && WaterReflectionCubemapResolutionMode == WaterReflectionCubemapResolutionModeEnumeration.Dynamic;
// 	public bool ShowWaterReflectionCubemapResolutionStatic => ShowWaterReflectionCubemapSettings && WaterReflectionCubemapResolutionMode == WaterReflectionCubemapResolutionModeEnumeration.Static;
// 	public bool ShowWaterReflectionCubemapFramesPerSecondDynamic => ShowWaterReflectionCubemapSettings && WaterReflectionCubemapFramesPerSecondMode == WaterReflectionCubemapFramesPerSecondModeEnumeration.Dynamic;
// 	public bool ShowWaterReflectionCubemapFramesPerSecondStatic => ShowWaterReflectionCubemapSettings && WaterReflectionCubemapFramesPerSecondMode == WaterReflectionCubemapFramesPerSecondModeEnumeration.Static;

// 	public enum WaterReflectionCubemapResolutionModeEnumeration
// 	{
// 		Dynamic,
// 		Static
// 	}

// 	[Property, Feature("Reflection"), Group("Cubemap"), Header("Resolution"), ShowIf( nameof(ShowWaterReflectionCubemapSettings), true )]
// 	public WaterReflectionCubemapResolutionModeEnumeration WaterReflectionCubemapResolutionMode {get;set;} = WaterReflectionCubemapResolutionModeEnumeration.Dynamic;

// 	[Property, Feature("Reflection"), Group("Cubemap"), ShowIf( nameof(ShowWaterReflectionCubemapResolutionDynamic), true )]
// 	public int WaterReflectionCubemapResolutionDynamicUltra {get;set;} = 1;

// 	[Property, Feature("Reflection"), Group("Cubemap"), ShowIf( nameof(ShowWaterReflectionCubemapResolutionDynamic), true )]
// 	public int WaterReflectionCubemapResolutionDynamicHigh {get;set;} = 2;

// 	[Property, Feature("Reflection"), Group("Cubemap"), ShowIf( nameof(ShowWaterReflectionCubemapResolutionDynamic), true )]
// 	public int WaterReflectionCubemapResolutionDynamicMedium {get;set;} = 4;

// 	[Property, Feature("Reflection"), Group("Cubemap"), ShowIf( nameof(ShowWaterReflectionCubemapResolutionDynamic), true )]
// 	public int WaterReflectionCubemapResolutionDynamicLow {get;set;} = 8;

// 	[Property, Feature("Reflection"), Group("Cubemap"), ShowIf( nameof(ShowWaterReflectionCubemapResolutionStatic), true )]
// 	public int WaterReflectionCubemapResolutionStatic {get;set;} = 2;

// 	/// <summary>
// 	///
// 	/// </summary>
// 	public enum WaterReflectionCubemapFramesPerSecondModeEnumeration
// 	{
// 		Dynamic,
// 		Static
// 	}

// 	[Property, Feature("Reflection"), Group("Cubemap"), Header("Frames Per Second"), ShowIf( nameof(ShowWaterReflectionCubemapSettings), true )]
// 	public WaterReflectionCubemapFramesPerSecondModeEnumeration WaterReflectionCubemapFramesPerSecondMode {get;set;} = WaterReflectionCubemapFramesPerSecondModeEnumeration.Dynamic;

// 	[Property, Feature("Reflection"), Group("Cubemap"), ShowIf( nameof(ShowWaterReflectionCubemapFramesPerSecondDynamic), true )]
// 	public float WaterReflectionCubemapFramesPerSecondDynamicUltra {get;set;} = 120f;

// 	[Property, Feature("Reflection"), Group("Cubemap"), ShowIf( nameof(ShowWaterReflectionCubemapFramesPerSecondDynamic), true )]
// 	public float WaterReflectionCubemapFramesPerSecondDynamicHigh {get;set;} = 60f;

// 	[Property, Feature("Reflection"), Group("Cubemap"), ShowIf( nameof(ShowWaterReflectionCubemapFramesPerSecondDynamic), true )]
// 	public float WaterReflectionCubemapFramesPerSecondDynamicMedium {get;set;} = 30f;

// 	[Property, Feature("Reflection"), Group("Cubemap"), ShowIf( nameof(ShowWaterReflectionCubemapFramesPerSecondDynamic), true )]
// 	public float WaterReflectionCubemapFramesPerSecondDynamicLow {get;set;} = 15f;

// 	[Property, Feature("Reflection"), Group("Cubemap"), ShowIf( nameof(ShowWaterReflectionCubemapFramesPerSecondStatic), true )]
// 	public float WaterReflectionCubemapFramesPerSecondStatic {get;set;} = 60f;
// }
ali3nsystems.lib-atmokinetic_assets / Code/Water/Modes/Tiles/WaterController.WaterTiles.Gizmo.cs
Game library
namespace AtmokineticAssets;

public sealed partial class WaterController : Component
{
	private void DrawWaterTileGizmo()
	{
		if ( !EnableWaterTileGizmo )
			return;

		if ( !Gizmo.IsSelected && !Gizmo.IsHovered )
			return;

		// Raw box from the user's typed bounds — used for the editing handles.
		var rawBox = new BBox(
			UnitConversion.CentimetersToInches( WaterTileBoundsMinimum ),
			UnitConversion.CentimetersToInches( WaterTileBoundsMaximum ) );

		// Snapped box — the actual world-grid region the tiles fill — used for drawing.
		var snappedBox = GetSnappedLocalBox( rawBox );

		DrawWaterTileBounds( snappedBox );
		DrawWaterTileHandles( rawBox );
	}

	// Snaps the bounds extent to the tile grid relative to the GameObject's own origin,
	// so the box keeps its tile-sized footprint but follows the GameObject smoothly (no world-grid jumping).
	private BBox GetSnappedLocalBox( BBox localBox )
	{
		float tileSizeInches = UnitConversion.CentimetersToInches( WaterTileSize );
		if ( tileSizeInches <= 0 )
			return localBox;

		float minX = MathF.Floor( MathF.Round( localBox.Mins.x / tileSizeInches, 4 ) ) * tileSizeInches;
		float minY = MathF.Floor( MathF.Round( localBox.Mins.y / tileSizeInches, 4 ) ) * tileSizeInches;
		float maxX = MathF.Ceiling( MathF.Round( localBox.Maxs.x / tileSizeInches, 4 ) ) * tileSizeInches;
		float maxY = MathF.Ceiling( MathF.Round( localBox.Maxs.y / tileSizeInches, 4 ) ) * tileSizeInches;

		return new BBox(
			new Vector3( minX, minY, localBox.Mins.z ),
			new Vector3( maxX, maxY, localBox.Maxs.z ) );
	}

	private void DrawWaterTileBounds( BBox box )
	{
		Gizmo.Draw.LineThickness = 1;
		var tint = Gizmo.IsSelected ? Gizmo.Colors.Blue : Gizmo.Colors.Blue.WithAlpha( 0.4f );

		for ( int i = 0; i < 2; i++ )
		{
			bool xray = i == 0;
			Gizmo.Draw.IgnoreDepth = xray;
			float alpha = xray ? 0.2f : 1f;

			Gizmo.Draw.Color = tint.WithAlpha( tint.a * alpha );
			Gizmo.Draw.LineBBox( box );

			Gizmo.Draw.Color = tint.WithAlpha( 0.05f * alpha );
			Gizmo.Draw.SolidBox( box );
		}
	}
	
	private void DrawWaterTileHandles( BBox box )
	{
		Gizmo.Hitbox.BBox( box );

		// Disable the editor's grid snap for this control so it doesn't double up with our tile snap.
		var previousSnap = Gizmo.Settings.SnapToGrid;
		Gizmo.Settings.SnapToGrid = false;

		if ( Gizmo.Control.BoundingBox( "water_tile_bounds", box, out var newBox ) )
		{
			var minCm = UnitConversion.InchesToCentimeters( newBox.Mins );
			var maxCm = UnitConversion.InchesToCentimeters( newBox.Maxs );

			WaterTileBoundsMinimum = SnapChangedAxes( WaterTileBoundsMinimum, minCm, WaterTileSize, floor: true );
			WaterTileBoundsMaximum = SnapChangedAxes( WaterTileBoundsMaximum, maxCm, WaterTileSize, floor: false );
		}

		Gizmo.Settings.SnapToGrid = previousSnap;
	}

	private float ResizeWaterTileBounds( float oldSize, float newSize )
	{
		// Skip during deserialization / before start — only rescale on genuine edits.
		if ( !_tilesStarted || oldSize == newSize || newSize <= 0 || oldSize <= 0 )
			return newSize;

		float scale = newSize / oldSize;

		// Scale X/Y of the bounds so the box stays the same tile count.
		WaterTileBoundsMinimum = new Vector3( WaterTileBoundsMinimum.x * scale, WaterTileBoundsMinimum.y * scale, WaterTileBoundsMinimum.z );
		WaterTileBoundsMaximum = new Vector3( WaterTileBoundsMaximum.x * scale, WaterTileBoundsMaximum.y * scale, WaterTileBoundsMaximum.z );

		return newSize;
	}

	private static Vector3 SnapChangedAxes( Vector3 oldValue, Vector3 newValue, float tileSize, bool floor )
	{
		if ( tileSize <= 0 )
			return newValue;

		float SnapAxis( float n ) =>
			( floor ? MathF.Floor( n / tileSize ) : MathF.Ceiling( n / tileSize ) ) * tileSize;

		return new Vector3(
			oldValue.x.AlmostEqual( newValue.x ) ? oldValue.x : SnapAxis( newValue.x ),
			oldValue.y.AlmostEqual( newValue.y ) ? oldValue.y : SnapAxis( newValue.y ),
			oldValue.z.AlmostEqual( newValue.z ) ? oldValue.z : newValue.z );
	}
}
ali3nsystems.lib-atmokinetic_assets / Code/Water/WaterController.Buoyancy.cs
Game library
namespace AtmokineticAssets;

public sealed partial class WaterController : Component, Component.ExecuteInEditor
{
    
   	/// <summary>
	/// How heavy the fluid is. Higher values make things float more.
	/// Water is 1000, oil is around 900, something very heavy like mercury is 13600.
	/// </summary>
	[Property, Feature("Buoyancy"), Group( "Fluid" ), Title("Fluid Density"), Range( 0, 15000 )]
	[Sync]
	public float WaterBuoyancyFluidDensity { get; set; } = 1000.0f;

	/// <summary>
	/// Direction and speed of the water current.
	/// </summary>
	[Property, Feature("Buoyancy"), Group( "Fluid" ), Title("Fluid Velocity")]
	[Sync]
	public Vector3 WaterBuoyancyFluidVelocity { get; set; } = Vector3.Zero;

	/// <summary>
	/// How much the fluid slows down movement.
	/// </summary>
	[Property, Feature("Buoyancy"), Group( "Fluid" ), Title("Linear Drag"),Range( 0, 1 )]
	[Sync]
	public float WaterBuoyancyLinearDrag { get; set; } = 0.1f;

	/// <summary>
	/// How much the fluid slows down spinning.
	/// </summary>
	[Property, Feature("Buoyancy"), Group( "Fluid" ), Title("Angular Drag"), Range( 0, 2 )]
	[Sync]
	public float WaterBuoyancyAngularDrag { get; set; } = 0.5f;

	/// <summary>
	/// Moves the water surface up or down.
	/// </summary>
	[Property, Feature("Buoyancy"), Group( "Surface" ), Title("Surface Offset")]
	[Sync]
	public float WaterBuoyancySurfaceOffset { get; set; } = 0.0f;

	/// <summary>
	/// How tall the waves are. Set to 0 for calm water.
	/// </summary>
	[Property, Feature("Buoyancy"), Group( "Surface" ), Title("Wave Amplitude"), Range( 0, 100 )]
	[Sync]
	public float WaterBuoyancyWaveAmplitude { get; set; } = 0.1f;

	/// <summary>
	/// How fast the waves move.
	/// </summary>
	[Property, Feature("Buoyancy"), Group( "Surface" ), Title("Wave Frequency"), Range( 0, 5 )]
	[Sync]
	public float WaterBuoyancyWaveFrequency { get; set; } = 0.5f;

	/// <summary>
	/// Objects with any of these tags won't be affected.
	/// </summary>
	[Property, Feature("Buoyancy"), Group( "Filtering" ), Title("Ignore Tags")]
	[Sync]
	public TagSet WaterBuoyancyIgnoreTags { get; set; } = [];

	private WaterVolume _waterBuoyancy;

	public void CreateWaterBuoyancy()
	{
		_waterBuoyancy ??= Components.Get<Sandbox.WaterVolume>( FindMode.InSelf ) ?? Components.Create<Sandbox.WaterVolume>();
		_waterBuoyancy.Flags |= ComponentFlags.Hidden;

		_waterBuoyancy.FluidDensity = WaterBuoyancyFluidDensity;
		_waterBuoyancy.FluidVelocity = WaterBuoyancyFluidVelocity;
		_waterBuoyancy.LinearDrag = WaterBuoyancyLinearDrag;
		_waterBuoyancy.AngularDrag = WaterBuoyancyAngularDrag;
		_waterBuoyancy.SurfaceOffset = _waterZ - WorldPosition.z + WaterBuoyancySurfaceOffset;
		_waterBuoyancy.WaveAmplitude = WaterBuoyancyWaveAmplitude;
		_waterBuoyancy.WaveFrequency = WaterBuoyancyWaveFrequency;
		_waterBuoyancy.IgnoreTags = WaterBuoyancyIgnoreTags;
	}
}
ali3nsystems.lib-atmokinetic_assets / Graphics/GraphicsSettings.TerrainSettings.cs
Game library
namespace AtmokineticAssets;

public sealed partial class GraphicsSettings : Component, Component.ExecuteInEditor
{
    //Terrain Settings
    public enum TerrainRenderingModeEnumeration
    {
        [Title("Parallax Occlusion Mapping")]
        ParallaxOcclusionMapping,
        
        [Title("Bump Offset")]
        BumpOffset,
       
        Standard
    }

    //[Property, Feature("Terrain"), Title("Rendering")]
    public TerrainRenderingModeEnumeration TerrainRenderingMode { get; set; } = TerrainRenderingModeEnumeration.Standard;

    public enum TerrainRenderingParallaxOcclusionMappingQualityEnumeration
    {
        Ultra,
        High,
        Medium,
        Low
    }

    //[Property, Feature("Terrain"), Title("Quality")] 
    [ShowIf(nameof(TerrainRenderingMode), TerrainRenderingModeEnumeration.ParallaxOcclusionMapping)]
    public TerrainRenderingParallaxOcclusionMappingQualityEnumeration TerrainRenderingParallaxOcclusionMapping { get; set; } = TerrainRenderingParallaxOcclusionMappingQualityEnumeration.Medium;

    public enum TerrainRenderingBumpOffsetQualityEnumeration
    {
        Ultra,
        High,
        Medium,
        Low
    }
   
    //[Property, Feature("Terrain"), Title("Quality")] 
    [ShowIf(nameof(TerrainRenderingMode), TerrainRenderingModeEnumeration.BumpOffset)]
    public TerrainRenderingParallaxOcclusionMappingQualityEnumeration TerrainRenderingBumpOffset { get; set; } = TerrainRenderingParallaxOcclusionMappingQualityEnumeration.Medium;


    // public void ChangeTerrainSettings()
    // {
    //             // Terrain rendering mode: 0 = POM, 1 = Bump Offset, 2 = Standard
    //     int terrainRenderingMode = TerrainRenderingMode switch
    //     {
    //         TerrainRenderingModeEnumeration.ParallaxOcclusionMapping => 0,
    //         TerrainRenderingModeEnumeration.BumpOffset => 1,
    //         TerrainRenderingModeEnumeration.Standard => 2,
    //         _ => 1
    //     };

    //     // Quality: Ultra = 1, High = 2, Medium = 4, Low = 8
    //     float quality;
    //     switch ( TerrainRenderingMode )
    //     {
    //         case TerrainRenderingModeEnumeration.ParallaxOcclusionMapping:
    //             quality = TerrainRenderingParallaxOcclusionMapping switch
    //             {
    //                 TerrainRenderingParallaxOcclusionMappingQualityEnumeration.Ultra => 1f,
    //                 TerrainRenderingParallaxOcclusionMappingQualityEnumeration.High => 2f,
    //                 TerrainRenderingParallaxOcclusionMappingQualityEnumeration.Medium => 4f,
    //                 TerrainRenderingParallaxOcclusionMappingQualityEnumeration.Low => 8f,
    //                 _ => 4f
    //             };
    //             break;

    //         case TerrainRenderingModeEnumeration.BumpOffset:
    //             quality = TerrainRenderingBumpOffset switch
    //             {
    //                 TerrainRenderingParallaxOcclusionMappingQualityEnumeration.Ultra => 1f,
    //                 TerrainRenderingParallaxOcclusionMappingQualityEnumeration.High => 2f,
    //                 TerrainRenderingParallaxOcclusionMappingQualityEnumeration.Medium => 4f,
    //                 TerrainRenderingParallaxOcclusionMappingQualityEnumeration.Low => 8f,
    //                 _ => 4f
    //             };
    //             break;

    //         default:
    //             quality = 4f;
    //             break;
    //     }

    //     // Enumerate live — objects spawned after startup (e.g. baked water tiles) must be included.
    //     foreach ( var sceneObject in Scene.SceneWorld.SceneObjects )
    //     {
    //         if ( sceneObject is null || !sceneObject.IsValid() ) continue;
    //         sceneObject.Attributes.Set( "D_Rendering", terrainRenderingMode );
    //         sceneObject.Attributes.Set( "Quality", quality );
    //     }
    // }
}
ali3nsystems.lib-atmokinetic_assets / Utility/UnitConversion.cs
Game library
namespace AtmokineticAssets;

public static class UnitConversion
{
	//Inches To Meters
	public static float InchesToMeters( float inches )
	{
		return inches * 0.0254f;
	}

	public static Vector2 InchesToMeters( Vector2 inches )
	{
		return inches * 0.0254f;
	}

	public static Vector3 InchesToMeters( Vector3 inches )
	{
		return inches * 0.0254f;
	}

	public static Vector4 InchesToMeters( Vector4 inches )
	{
		return inches * 0.0254f;
	}



	//Inches To Centimeters
	public static float InchesToCentimeters( float inches)
	{
		return inches * 2.54f;
	}

	public static Vector2 InchesToCentimeters( Vector2 inches)
	{
		return inches * 2.54f;
	}

	public static Vector3 InchesToCentimeters( Vector3 inches)
	{
		return inches * 2.54f;
	}

	public static Vector4 InchesToCentimeters( Vector4 inches)
	{
		return inches * 2.54f;
	}

	//Centimeters To Inches
	public static float CentimetersToInches( float centimeters )
	{
		return centimeters / 2.54f;
	}
	
	public static Vector2 CentimetersToInches( Vector2 centimeters )
	{
		return centimeters / 2.54f;
	}
	
	public static Vector3 CentimetersToInches( Vector3 centimeters )
	{
		return centimeters / 2.54f;
	}
	
	public static Vector4 CentimetersToInches( Vector4 centimeters )
	{
		return centimeters / 2.54f;
	}

	//Meters To Centimeters
	public static float MetersToCentimeters( float meters )
	{
		return meters * 100f;
	}

	//Meters To Inches
	public static float MetersToInches(float meters)
	{
		return meters / 0.0254f;
	}
}
ali3nsystems.lib-atmokinetic_assets / Water/Modes/WorkInProgress/Area/WaterController.WaterArea.Gizmo.cs
Game library
namespace AtmokineticAssets;

public sealed partial class WaterController: Component
{
	// [Property, Title("Enable Water Gizmo"),Feature( "General" ), ShowIf( nameof(WaterMode), WaterModeEnumeration.Area)]
	// public bool EnableWaterAreaGizmo { get; set; } = true;
}
ali3nsystems.lib-atmokinetic_assets / Water/WaterController.Surface.cs
Game library
namespace AtmokineticAssets;

// Also add:

//Ice
//Caustics

public sealed partial class WaterController : Component, Component.ExecuteInEditor
{
	// Surface
	[Property, Feature("Attributes"), Group("Surface"), Title("Color"), Order(1)]
	[Sync]
	public Color WaterSurfaceColor { get; set { field = value; _surfaceAttributesDirty = true; } } = Color.Parse( "#c5d9ff" ).Value;

	[Property, Feature("Attributes"), Group("Surface"), Title("Blur"), Order(2)]
	[Sync]
	public float WaterSurfaceBlur { get; set { field = value; _surfaceAttributesDirty = true; } } = 0;

	// Reflection
	[Property, Feature("Attributes"), Group("Reflection"), Title("Color"), Order(3)]
	[Sync]
	public Color WaterReflectionColor { get; set { field = value; _surfaceAttributesDirty = true; } } = Color.Parse( "#c5d9ff" ).Value;

	[Property, Feature("Attributes"), Group("Reflection"), Title("Blur"), Order(4)]
	[Sync]
	public float WaterReflectionBlur { get; set { field = value; _surfaceAttributesDirty = true; } } = 0;

	[Property, Feature("Attributes"), Group("Reflection"), Title("Distance"), Order(5)]
	[Sync]
	public float WaterReflectionDistance { get; set { field = value; _surfaceAttributesDirty = true; } } = 4f;

	[Property, Feature("Attributes"), Group("Reflection"), Title("Threshold"), Order(6)]
	[Sync]
	public Vector2 WaterReflectionThreshold { get; set { field = value; _surfaceAttributesDirty = true; } } = new Vector2( 0, 0.25f );

	// Shoreline
	[Property, Feature("Attributes"), Group("Shoreline"), Title("Fade"), Order(7)]
	[Sync]
	public float WaterShorelineFade { get; set { field = value; _surfaceAttributesDirty = true; } } = 2f;

	// Tension
	[Property, Feature("Attributes"), Group("Tension"), Title("Distance"), Order(8)]
	[Sync]
	public float WaterTensionDistance { get; set { field = value; _surfaceAttributesDirty = true; } } = 2f;

	[Property, Feature("Attributes"), Group("Tension"), Title("Refraction"), Order(9)]
	[Sync]
	public float WaterTensionRefraction { get; set { field = value; _surfaceAttributesDirty = true; } } = 0.1f;

	// Texture A
	[Property, Feature("Attributes"), Group("Water Ripples A"), Title("Texture A"), Order(10)]
	[Sync]
	public Texture WaterRipplesTextureA { get; set { field = value; _surfaceAttributesDirty = true; } } = Texture.Load("textures/tex-water_ripples_1.vtex");

	[Property, Feature("Attributes"), Group("Water Ripples A"), Title("Scale A"), Order(11)]
	[Sync]
	public float WaterRipplesScaleA { get; set { field = value; _surfaceAttributesDirty = true; } } = 0.01f;

	[Property, Feature("Attributes"), Group("Water Ripples A"), Title("Rotation A"), Order(12)]
	[Sync]
	public float WaterRipplesRotationA { get; set { field = value; _surfaceAttributesDirty = true; } } = 0f;

	[Property, Feature("Attributes"), Group("Water Ripples A"), Title("Speed A"), Order(13)]
	[Sync]
	public float WaterRipplesSpeedA { get; set { field = value; _surfaceAttributesDirty = true; } } = 0.1f;

	[Property, Feature("Attributes"), Group("Water Ripples A"), Title("Refraction A"), Order(14)]
	[Sync]
	public float WaterRipplesRefractionA { get; set { field = value; _surfaceAttributesDirty = true; } } = 0.05f;

	[Property, Feature("Attributes"), Group("Water Ripples A"), Title("Displacement A"), Order(15)]
	[Sync]
	public float WaterRipplesDisplacementA { get; set { field = value; _surfaceAttributesDirty = true; } } = 0.5f;

	// Texture B
	[Property, Feature("Attributes"), Group("Water Ripples B"), Title("Texture B"), Order(16)]
	[Sync]
	public Texture WaterRipplesTextureB { get; set { field = value; _surfaceAttributesDirty = true; } } = Texture.Load("textures/tex-water_ripples_2.vtex");

	[Property, Feature("Attributes"), Group("Water Ripples B"), Title("Scale B"), Order(17)]
	[Sync]
	public float WaterRipplesScaleB { get; set { field = value; _surfaceAttributesDirty = true; } } = 0.01f;

	[Property, Feature("Attributes"), Group("Water Ripples B"), Title("Rotation B"), Order(18)]
	[Sync]
	public float WaterRipplesRotationB { get; set { field = value; _surfaceAttributesDirty = true; } } = 180f;

	[Property, Feature("Attributes"), Group("Water Ripples B"), Title("Speed B"), Order(19)]
	[Sync]
	public float WaterRipplesSpeedB { get; set { field = value; _surfaceAttributesDirty = true; } } = 0.1f;

	[Property, Feature("Attributes"), Group("Water Ripples B"), Title("Refraction B"), Order(20)]
	[Sync]
	public float WaterRipplesRefractionB { get; set { field = value; _surfaceAttributesDirty = true; } } = 0.05f;

	[Property, Feature("Attributes"), Group("Water Ripples B"), Title("Displacement B"), Order(21)]
	[Sync]
	public float WaterRipplesDisplacementB { get; set { field = value; _surfaceAttributesDirty = true; } } = 0.5f;

	// Starts dirty so the first WaterSurfaceAttributes call always pushes.
	private bool _surfaceAttributesDirty = true;

	[Rpc.Broadcast]
	public void WaterSurfaceAttributes()
	{
		if ( !_surfaceAttributesDirty )
			return;

		_surfaceAttributesDirty = false;

		foreach ( var renderer in GetTileRenderers() )
		{

			if (WaterSurfaceBlur != 0)
			{	
				renderer.Attributes.SetCombo("D_Enable_Water_Surface_Blur", 1);
			}
			else
			{
				renderer.Attributes.SetCombo("D_Enable_Water_Surface_Blur", 0);
			}

			if (WaterReflectionBlur != 0)
			{
				renderer.Attributes.SetCombo("D_Enable_Water_Reflection_Blur", 1);
			}
			else
			{
				renderer.Attributes.SetCombo("D_Enable_Water_Reflection_Blur", 0);
			}


			// Surface
			renderer.Attributes.Set( "Water Surface Color", WaterSurfaceColor );
			renderer.Attributes.Set( "Water Surface Blur", WaterSurfaceBlur );

			// Reflection
			renderer.Attributes.Set( "Water Reflection Color", WaterReflectionColor );
			renderer.Attributes.Set( "Water Reflection Blur", WaterReflectionBlur );
			renderer.Attributes.Set( "Water Reflection Distance", WaterReflectionDistance );
			renderer.Attributes.Set( "Water Reflection Threshold", WaterReflectionThreshold );

			// Shoreline
			renderer.Attributes.Set( "Water Shoreline Fade", WaterShorelineFade );
			
			// Tension
			renderer.Attributes.Set( "Water Tension Distance", WaterTensionDistance );
			renderer.Attributes.Set( "Water Tension Refraction", WaterTensionRefraction );

			// Water Ripples A
			renderer.Attributes.Set( "WaterRipplesTextureA", WaterRipplesTextureA );
			renderer.Attributes.Set( "Water Ripples Scale A", WaterRipplesScaleA );
			renderer.Attributes.Set( "Water Ripples Rotation A", WaterRipplesRotationA );
			renderer.Attributes.Set( "Water Ripples Speed A", WaterRipplesSpeedA );
			renderer.Attributes.Set( "Water Ripples Refraction A", WaterRipplesRefractionA );
			renderer.Attributes.Set( "Water Ripples Displacement A", WaterRipplesDisplacementA );

			// Water Ripples B
			renderer.Attributes.Set( "WaterRipplesTextureB", WaterRipplesTextureB );
			renderer.Attributes.Set( "Water Ripples Scale B", WaterRipplesScaleB );
			renderer.Attributes.Set( "Water Ripples Rotation B", WaterRipplesRotationB );
			renderer.Attributes.Set( "Water Ripples Speed B", WaterRipplesSpeedB );
			renderer.Attributes.Set( "Water Ripples Refraction B", WaterRipplesRefractionB );
			renderer.Attributes.Set( "Water Ripples Displacement B", WaterRipplesDisplacementB );
		}
	}
}
ali3nsystems.lib-atmokinetic_assets / Environment/EnvironmentController.cs
Game library
namespace AtmokineticAssets;

[Title("Atmokinetic Assets - Environment Controller")]
[Category("Atmokinetic Assets")]
[Icon("cloud")]

public sealed partial class EnvironmentController : Component
{
	protected override void OnAwake()
	{
		CacheWaterControllers();
	}

	protected override void OnUpdate()
	{
		
	} 

	// public enum PrecipitationModeEnumeration
	// {
	// 	None,
	// 	Rain,
	// 	Snow
	// }

	// [Property, Feature("Weather"), Sync]
	// public PrecipitationModeEnumeration PrecipitationMode { get; set; } = PrecipitationModeEnumeration.None;

	// [Property, Feature("Weather")] PrefabScene PrecipitationController { get; set;}

	// [Property, Feature("Weather"), Range( 0f, 1f ), Sync]
	// public float WaterPuddles { get; set; } = 0f;

	// [Property, Feature("Weather"), Range( 0.0001f, 1f ), Sync]
	// public float WaterLevel { get; set; } = 0f;

	// [Property, Feature("Weather"), Range( 0, 4 ), Step(1), Sync]
	// public float PrecipitationIntensity { get; set; } = 0f;

	// [Property,Feature("Time"), Sync]
	// public GameObject Sun {get;set;}

	// [Property, Feature("Time"), Range(0,24), Sync]
	// public float Hour {get;set;} = 6f;
	
	// [Property, Feature("Time"), Range(1,31), Step(1)]
	// public float Day {get;set;} = 0f;
	
	// [Property, Feature("Time"), Range(1,12), Step(1)]
	// public float Month {get;set;} = 0f;
	
	// [Property, Feature("Time"), Step(1)]
	// public float Year {get;set;} = 0f;

	// private float _lastWaterPuddlesValue = -1f;
	// private float _lastPrecipitationIntensityValue = -1f;
	// private float _lastPrecipitationIntensityValueForParticles = -1f;
	// private float _lastWaterLevelValue = -1f;
	
	// private List<SceneObject> _cachedSceneObjects;
	// private List<EnvmapProbe> _cachedEnvmapProbes;

	// // Rain Precipitation
	// private GameObject _rainGameObject;
	// private ParticleEffect _particleEffectRain;
	// private PrecipitationController _precipitationControllerRain;
	// private PrecipitationControllerEmitter _precipitationControllerRainEmitter;
	// private bool _activePrecipitationControllerRain;

	// // Snow Precipitation
	// private GameObject _snowGameObject;
	// private ParticleEffect _particleEffectSnow;
	// private PrecipitationController _precipitationControllerSnow;
	// private PrecipitationControllerEmitter _precipitationControllerSnowEmitter;
	// private bool _activePrecipitationControllerSnow;

	// private PrecipitationModeEnumeration _lastPrecipitationMode = PrecipitationModeEnumeration.None;

	// // Lerp state
	// private float _startMaxParticles;
	// private float _startRate;
	// private float _targetMaxParticles;
	// private float _targetRate;
	// private float _transitionProgress = 1f;
	// private float _lastHour = -1f;

	// protected override void OnStart()
	// {
	// 	// Cache all scene objects on startup
	// 	_cachedSceneObjects = Scene.SceneWorld.SceneObjects.Where( x => x != null ).ToList();

	// 	// Cache all environment map probes
	// 	_cachedEnvmapProbes = Scene.GetAllComponents<EnvmapProbe>().ToList();
	// }
	
	// [Rpc.Broadcast]
	// protected override void OnUpdate()
	// {
	// 	HandlePrecipitationModeChange();
	// 	PrecipitationEffects();
	// 	TimeEffects();
	// }

	// protected override void DrawGizmos()
	// {
	// 	if (_cachedSceneObjects == null)
	// 	{
	// 		_cachedSceneObjects = Scene.SceneWorld.SceneObjects.Where( x => x != null ).ToList();
	// 	}

	// 	if (_cachedEnvmapProbes == null)
	// 	{
	// 		_cachedEnvmapProbes = Scene.GetAllComponents<EnvmapProbe>().ToList();
	// 	}

	// 	if ( WaterPuddles != _lastWaterPuddlesValue || WaterLevel != _lastWaterLevelValue || PrecipitationIntensity != _lastPrecipitationIntensityValue)
	// 	{
	// 		_lastWaterPuddlesValue = WaterPuddles;
	// 		_lastWaterLevelValue = WaterLevel;
	// 		_lastPrecipitationIntensityValue = PrecipitationIntensity;

	// 		foreach ( var sceneModel in _cachedSceneObjects )
	// 		{
	// 			sceneModel.Attributes.Set( "Water Puddles Accumulation", WaterPuddles );
	// 			sceneModel.Attributes.Set( "Water Plane Level", WaterLevel );
	// 			sceneModel.Attributes.Set( "Rain Ripples Intensity", PrecipitationIntensity );
	// 		}
	// 	}

	// 	TimeEffects();
	// }

	// private void HandlePrecipitationModeChange()
	// {
	// 	if (PrecipitationMode == _lastPrecipitationMode) return;

	// 	// Only host spawns networked objects

	// 	// Handle mode transitions
	// 	if (_lastPrecipitationMode == PrecipitationModeEnumeration.Rain)
	// 	{
	// 		TransitionOutRain();
	// 	}
	// 	else if (_lastPrecipitationMode == PrecipitationModeEnumeration.Snow)
	// 	{
	// 		TransitionOutSnow();
	// 	}

	// 	// Spawn new precipitation if not None
	// 	if (PrecipitationMode == PrecipitationModeEnumeration.Rain)
	// 	{
	// 		SpawnRain();
	// 	}
	// 	else if (PrecipitationMode == PrecipitationModeEnumeration.Snow)
	// 	{
	// 		SpawnSnow();
	// 	}

	// 	_lastPrecipitationMode = PrecipitationMode;
	// }

	// private void SpawnRain()
	// {
	// 	// Reset transition state to trigger fresh lerp
	// 	_lastPrecipitationIntensityValue = -1f;
	// 	_lastPrecipitationIntensityValueForParticles = -1f;

	// 	// If rain instance already exists (transitioning out), reactivate it
	// 	if (_precipitationControllerRain.IsValid())
	// 	{
	// 		_activePrecipitationControllerRain = true;
	// 		return;
	// 	}

	// 	if (!PrecipitationController.IsValid()) return;

	// 	var clonedGameObject = PrecipitationController.Clone();
	// 	clonedGameObject.Name = "Precipitation Controller (Rain)";

	// 	_rainGameObject = clonedGameObject;
	// 	_particleEffectRain = clonedGameObject.GetComponent<ParticleEffect>();
	// 	_precipitationControllerRain = clonedGameObject.GetComponent<PrecipitationController>();
	// 	_precipitationControllerRainEmitter = clonedGameObject.GetComponent<PrecipitationControllerEmitter>();

	// 	_precipitationControllerRain.PrecipitationMode = AtmokineticAssets.PrecipitationController.PrecipitationModeEnumeration.Rain;

	// 	_particleEffectRain.MaxParticles = 0;
	// 	_precipitationControllerRainEmitter.Rate = 0;
	// 	_activePrecipitationControllerRain = true;

	// 	// Reset transition to ensure smooth lerp from current state
	// }

	// private void SpawnSnow()
	// {
	// 	// Reset transition state to trigger fresh lerp
	// 	_lastPrecipitationIntensityValue = -1f;
	// 	_lastPrecipitationIntensityValueForParticles = -1f;

	// 	// If snow instance already exists (transitioning out), reactivate it
	// 	if (_precipitationControllerSnow.IsValid())
	// 	{
	// 		_activePrecipitationControllerSnow = true;
	// 		return;
	// 	}

	// 	if (!PrecipitationController.IsValid()) return;

	// 	var clonedGameObject = PrecipitationController.Clone();
	// 	clonedGameObject.Name = "Precipitation Controller (Snow)";

	// 	_snowGameObject = clonedGameObject;
	// 	_particleEffectSnow = clonedGameObject.GetComponent<ParticleEffect>();
	// 	_precipitationControllerSnow = clonedGameObject.GetComponent<PrecipitationController>();
	// 	_precipitationControllerSnowEmitter = clonedGameObject.GetComponent<PrecipitationControllerEmitter>();

	// 	_precipitationControllerSnow.PrecipitationMode = AtmokineticAssets.PrecipitationController.PrecipitationModeEnumeration.Snow;

	// 	_particleEffectSnow.MaxParticles = 0;
	// 	_precipitationControllerSnowEmitter.Rate = 0;
	// 	_activePrecipitationControllerSnow = true;

	// 	// Reset transition to ensure smooth lerp from current state
	// }

	// private void TransitionOutRain() => _activePrecipitationControllerRain = false;

	// private void TransitionOutSnow() => _activePrecipitationControllerSnow = false;
	
	// public void PrecipitationEffects()
	// {
	// 	if (_precipitationControllerRain.IsValid())
	// 	{
	// 		if (_activePrecipitationControllerRain)
	// 		{
	// 			UpdatePrecipitationTransition(
	// 				_particleEffectRain,
	// 				_precipitationControllerRainEmitter,
	// 				_precipitationControllerRain.RainIntensityMaximumParticlesLight,
	// 				_precipitationControllerRain.RainIntensityRateLight,
	// 				_precipitationControllerRain.RainIntensityMaximumParticlesModerate,
	// 				_precipitationControllerRain.RainIntensityRateModerate,
	// 				_precipitationControllerRain.RainIntensityMaximumParticlesHeavy,
	// 				_precipitationControllerRain.RainIntensityRateHeavy,
	// 				_precipitationControllerRain.RainIntensityMaximumParticlesExtreme,
	// 				_precipitationControllerRain.RainIntensityRateExtreme,
	// 				_precipitationControllerRain.RainParticleTransition
	// 			);
	// 		}
	// 		else
	// 		{
	// 			TransitionPrecipitationToZero(_particleEffectRain, _precipitationControllerRainEmitter, _precipitationControllerRain.RainParticleTransition);
	// 		}
	// 	}

	// 	if (_precipitationControllerSnow.IsValid())
	// 	{
	// 		if (_activePrecipitationControllerSnow)
	// 		{
	// 			UpdatePrecipitationTransition(
	// 				_particleEffectSnow,
	// 				_precipitationControllerSnowEmitter,
	// 				_precipitationControllerSnow.SnowIntensityMaximumParticlesLight,
	// 				_precipitationControllerSnow.SnowIntensityRateLight,
	// 				_precipitationControllerSnow.SnowIntensityMaximumParticlesModerate,
	// 				_precipitationControllerSnow.SnowIntensityRateModerate,
	// 				_precipitationControllerSnow.SnowIntensityMaximumParticlesHeavy,
	// 				_precipitationControllerSnow.SnowIntensityRateHeavy,
	// 				_precipitationControllerSnow.SnowIntensityMaximumParticlesExtreme,
	// 				_precipitationControllerSnow.SnowIntensityRateExtreme,
	// 				_precipitationControllerSnow.SnowParticleTransition
	// 			);
	// 		}
	// 		else
	// 		{
	// 			TransitionPrecipitationToZero(_particleEffectSnow, _precipitationControllerSnowEmitter, _precipitationControllerSnow.SnowParticleTransition);
	// 		}
	// 	}

	// 	if ( WaterPuddles != _lastWaterPuddlesValue || WaterLevel != _lastWaterLevelValue || PrecipitationIntensity != _lastPrecipitationIntensityValue)
	// 	{
	// 		_lastWaterPuddlesValue = WaterPuddles;
	// 		_lastWaterLevelValue = WaterLevel;
	// 		_lastPrecipitationIntensityValue = PrecipitationIntensity;

	// 		foreach ( var sceneModel in _cachedSceneObjects )
	// 		{
	// 			sceneModel.Attributes.Set( "Water Puddles Accumulation", WaterPuddles );
	// 			sceneModel.Attributes.Set( "Water Plane Level", WaterLevel );
	// 			sceneModel.Attributes.Set( "Rain Ripples Intensity", PrecipitationIntensity );
	// 		}
	// 	}
	// }

	// private void UpdatePrecipitationTransition(
	// 	ParticleEffect particleEffect,
	// 	PrecipitationControllerEmitter emitter,
	// 	int lightMaxParticles, int lightRate,
	// 	int moderateMaxParticles, int moderateRate,
	// 	int heavyMaxParticles, int heavyRate,
	// 	int extremeMaxParticles, int extremeRate,
	// 	float transitionDuration)
	// {
	// 	if ( PrecipitationIntensity != _lastPrecipitationIntensityValueForParticles )
	// 	{
	// 		_lastPrecipitationIntensityValueForParticles = PrecipitationIntensity;

	// 		_startMaxParticles = particleEffect.MaxParticles;
	// 		_startRate = emitter.Rate.ConstantValue;

	// 		int intensity = (int)PrecipitationIntensity;
	// 		(_targetMaxParticles, _targetRate) = intensity switch
	// 		{
	// 			1 => (lightMaxParticles, lightRate),
	// 			2 => (moderateMaxParticles, moderateRate),
	// 			3 => (heavyMaxParticles, heavyRate),
	// 			4 => (extremeMaxParticles, extremeRate),
	// 			_ => (0, 0)
	// 		};
	// 		_transitionProgress = 0f;
	// 	}

	// 	if ( _transitionProgress < 1f )
	// 	{
	// 		_transitionProgress += Time.Delta / transitionDuration;
	// 		if ( _transitionProgress >= 1f )
	// 		{
	// 			_transitionProgress = 1f;
	// 			particleEffect.MaxParticles = (int)_targetMaxParticles;
	// 			emitter.Rate = _targetRate;
	// 		}
	// 		else
	// 		{
	// 			particleEffect.MaxParticles = (int)(_startMaxParticles + (_targetMaxParticles - _startMaxParticles) * _transitionProgress);
	// 			emitter.Rate = _startRate + (_targetRate - _startRate) * _transitionProgress;
	// 		}
	// 	}
	// }

	// private void TransitionPrecipitationToZero(ParticleEffect particleEffect, PrecipitationControllerEmitter emitter, float transitionDuration)
	// {
	// 	float step = Time.Delta / transitionDuration;
	// 	particleEffect.MaxParticles = (int)Math.Max(0, particleEffect.MaxParticles - (particleEffect.MaxParticles * step));
	// 	emitter.Rate = Math.Max(0, emitter.Rate.ConstantValue - (emitter.Rate.ConstantValue * step));

	// 	if (particleEffect.MaxParticles == 0)
	// 	{
	// 		if (_precipitationControllerRain.IsValid() && particleEffect == _particleEffectRain)
	// 		{
	// 			_rainGameObject?.Destroy();
	// 			_rainGameObject = null;
	// 			_precipitationControllerRain = null;
	// 			_particleEffectRain = null;
	// 			_precipitationControllerRainEmitter = null;
	// 		}
	// 		else if (_precipitationControllerSnow.IsValid() && particleEffect == _particleEffectSnow)
	// 		{
	// 			_snowGameObject?.Destroy();
	// 			_snowGameObject = null;
	// 			_precipitationControllerSnow = null;
	// 			_particleEffectSnow = null;
	// 			_precipitationControllerSnowEmitter = null;
	// 		}
	// 	}
	// }
	// public void TimeEffects()
	// {
	// 	if ( Hour != _lastHour)
	// 	{
	// 		_lastHour = Hour;

	// 		const float sunriseStart = 5.6f;
	// 		const float sunrisePeak  = 6.3f;
	// 		const float sunriseEnd   = 7f;
	// 		const float sunsetStart  = 17f;
	// 		const float sunsetPeak   = 18.0f;
	// 		const float sunsetEnd    = 18.4f;
	// 		const float degreesPerHour = 15f;
	// 		const float sunAngleOffset = 90f;

	// 		Sun.WorldRotation = Rotation.From( (Hour * degreesPerHour) - sunAngleOffset, 0, 0 );

	// 		var directionalLight = Sun.Components.Get<DirectionalLight>();
	// 		if ( directionalLight.IsValid() )
	// 		{
	// 			var goldenHour = new Color( 1.0f, 0.45f, 0.1f );
	// 			Color sunColor;

	// 			if ( Hour >= sunsetEnd || Hour < sunriseStart )
	// 			{
	// 				sunColor = Color.Black;
	// 			}
	// 			else if ( Hour >= sunriseStart && Hour < sunrisePeak )
	// 			{
	// 				float time = (Hour - sunriseStart) / (sunrisePeak - sunriseStart);
	// 				sunColor = Color.Lerp( Color.Black, goldenHour, time );
	// 			}
	// 			else if ( Hour >= sunrisePeak && Hour < sunriseEnd )
	// 			{
	// 				float time = (Hour - sunrisePeak) / (sunriseEnd - sunrisePeak);
	// 				sunColor = Color.Lerp( goldenHour, Color.White, time );
	// 			}
	// 			else if ( Hour >= sunriseEnd && Hour < sunsetStart )
	// 			{
	// 				sunColor = Color.White;
	// 			}
	// 			else if ( Hour >= sunsetStart && Hour < sunsetPeak )
	// 			{
	// 				float time = (Hour - sunsetStart) / (sunsetPeak - sunsetStart);
	// 				sunColor = Color.Lerp( Color.White, goldenHour, time );
	// 			}
	// 			else
	// 			{
	// 				float time = (Hour - sunsetPeak) / (sunsetEnd - sunsetPeak);
	// 				sunColor = Color.Lerp( goldenHour, Color.Black, time );
	// 			}

	// 			directionalLight.LightColor = sunColor;
	// 		}

	// 		foreach ( var envmapProbe in _cachedEnvmapProbes )
	// 		{
	// 			if ( envmapProbe.IsValid() )
	// 			{
	// 				envmapProbe.Dirty = true;
	// 			}
	// 		}
	// 	}
	// }
}
ali3nsystems.lib-atmokinetic_assets / Water/Done/Volume/Cube/WaterController.VolumeCube.cs
Game library
// namespace AtmokineticAssets;

// [StructLayout(LayoutKind.Sequential)]
// public struct WetnessVolumeCubeBuffer
// {
// 	public Vector3 VolumeCenter;
// 	public Vector3 VolumeSize;
// 	public float WetnessVolumeExtend;
// 	public float WetnessVolumeFade;
// 	public float WetnessVolumeSoftness;
// }

// public sealed partial class WaterController : Component
// {
// 	[Property, Title("Bounds Minimum (cm)"), Feature("Wetness"), Group("Properties"), ShowIf( nameof(WetnessVolumeMode), WetnessVolumeModeEnumeration.Cube )]
// 	public Vector3 WetnessVolumeCubeBoundsMinimum {get;set;} = new Vector3(-500, -500, -500);

// 	[Property, Title("Bounds Maximum (cm)"), Feature("Wetness"), Group("Properties"), ShowIf( nameof(WetnessVolumeMode), WetnessVolumeModeEnumeration.Cube )]
// 	public Vector3 WetnessVolumeCubeBoundsMaximum {get;set;} = new Vector3( 500, 500, 500 );

// 	[Property, Title( "Enable Gizmo" ), Feature("Wetness"), Group("Properties"), ShowIf( nameof(WetnessVolumeMode), WetnessVolumeModeEnumeration.Cube )]
// 	public bool EnableWetnessVolumeCubeGizmos { get; set; } = true;

// 	private static readonly List<WaterController> _wetnessVolumeCubeList = new();
// 	private static GpuBuffer<WetnessVolumeCubeBuffer> _wetnessVolumeCubeBuffer;
// 	private static int _currentWetnessVolumeCubeBuffer = 0;

// 	public void EnableWetnessVolumeCubeBuffer()
// 	{
// 		if (!_wetnessVolumeCubeList.Contains(this))
// 			_wetnessVolumeCubeList.Add(this);

// 		if (_wetnessVolumeCubeBuffer == null)
// 		{
// 			_wetnessVolumeCubeBuffer = new GpuBuffer<WetnessVolumeCubeBuffer>(1);
// 			_currentWetnessVolumeCubeBuffer = 1;
// 		}
// 		UpdateWetnessVolumeCubeBuffer();
// 	}

// 	public void DisableWetnessVolumeCubeBuffer()
// 	{
// 		_wetnessVolumeCubeList.Remove( this );
// 		UpdateWetnessVolumeCubeBuffer();
// 	}

// 	public static void UpdateWetnessVolumeCubeBuffer()
// 	{
// 		if (_wetnessVolumeCubeList.Count == 0) return;

// 		var volumeBufferList = new List<WetnessVolumeCubeBuffer>();

// 		foreach (var wetnessVolumeCube in _wetnessVolumeCubeList)
// 		{
// 			if (!wetnessVolumeCube.IsValid()) continue;

// 			// Convert cm to inches
// 			var boundsMinimumInches = UnitConversion.CentimetersToInches(wetnessVolumeCube.WetnessVolumeCubeBoundsMinimum);
// 			var boundsMaximumInches = UnitConversion.CentimetersToInches(wetnessVolumeCube.WetnessVolumeCubeBoundsMaximum);

// 			// Calculate world space center and size
// 			Vector3 localCenter = (boundsMinimumInches + boundsMaximumInches) * 0.5f;
// 			Vector3 worldCenter = wetnessVolumeCube.WorldPosition + localCenter;
// 			Vector3 size = boundsMaximumInches - boundsMinimumInches;

// 			volumeBufferList.Add(new WetnessVolumeCubeBuffer
// 			{
// 				VolumeCenter = worldCenter,
// 				WetnessVolumeExtend = UnitConversion.CentimetersToInches( wetnessVolumeCube.WetnessVolumeExtend ),
// 				VolumeSize = size,
// 				WetnessVolumeFade = wetnessVolumeCube.WetnessVolumeFade,
// 				WetnessVolumeSoftness = wetnessVolumeCube.WetnessVolumeSoftness,

// 			});
// 		}

// 		if (_wetnessVolumeCubeBuffer == null || volumeBufferList.Count > _currentWetnessVolumeCubeBuffer)
// 		{
// 			_wetnessVolumeCubeBuffer = new GpuBuffer<WetnessVolumeCubeBuffer>(volumeBufferList.Count);
// 			_currentWetnessVolumeCubeBuffer = volumeBufferList.Count;
// 		}

// 		if (volumeBufferList.Count > 0)
// 		{
// 			_wetnessVolumeCubeBuffer.SetData(volumeBufferList);
// 		}

// 		if (Game.ActiveScene?.SceneWorld != null)
// 		{
// 			var sceneObjects = Game.ActiveScene.SceneWorld.SceneObjects
// 				.Where(x => x != null && x.IsValid())
// 				.ToList();

// 			foreach (var sceneObject in sceneObjects)
// 			{
// 				if (sceneObject.IsValid())
// 				{
// 					sceneObject.Attributes.Set("WetnessWetnessVolumeCubeBuffer", _wetnessVolumeCubeBuffer);
// 					sceneObject.Attributes.Set("MaximumWetnessWetnessVolumeCubes", volumeBufferList.Count);
// 				}
// 			}
// 		}
// 	}
// }
ali3nsystems.lib-atmokinetic_assets / Water/Reflection/WaterController.Reflection.cs
Game library
namespace AtmokineticAssets;

public sealed partial class WaterController : Component, Component.ExecuteInEditor
{
	public enum WaterReflectionModeEnumeration
	{
		Dynamic,
		Static
	}

	[Property, Feature("Reflection"), Header("Mode")]
	[Sync]
	public WaterReflectionModeEnumeration WaterReflectionMode
	{
		get;
		set { field = value; ApplyWaterReflectionMode(); }
	} = WaterReflectionModeEnumeration.Dynamic;

	public enum WaterReflectionModeStaticModeEnumeration
	{
		Planar,
		Cubemap
	}

	[Property, Feature("Reflection"), ShowIf( nameof(WaterReflectionMode), WaterReflectionModeEnumeration.Static)]
	[Sync]
	public WaterReflectionModeStaticModeEnumeration WaterReflectionModeStaticMode
	{
		get;
		set { field = value; ApplyWaterReflectionMode(); }
	} = WaterReflectionModeStaticModeEnumeration.Planar;

	// Mark the reflection mode dirty after its own settings change. Deliberately does
	// NOT apply immediately: these setters also fire from [Sync] snapshots while a
	// client is joining, before the scene camera exists - frustum work here NREs.
	// OnUpdate's change-check applies it on the next frame instead.
	private void ApplyWaterReflectionMode()
	{
		_lastWaterReflectionPlanarRendering = null;
	}

	public Vector3 WaterNormalAxis { get; set; } = Vector3.Up;
	
}
ali3nsystems.lib-atmokinetic_assets / .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", "Atmokinetic Assets" )]
[assembly: global::System.Reflection.AssemblyMetadata( "AddonIdent", "lib-atmokinetic_assets" )]
[assembly: global::System.Reflection.AssemblyMetadata( "OrgIdent", "ali3nsystems" )]
[assembly: global::System.Reflection.AssemblyMetadata( "Ident", "ali3nsystems.lib-atmokinetic_assets" )]
[assembly: global::System.Reflection.AssemblyMetadata( "EngineVersion", "28" )]
[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-08-03T22:07:53.7475400Z" )]
[assembly: global::System.Reflection.AssemblyVersion("0.0.111.0")]
[assembly: global::System.Reflection.AssemblyFileVersion("0.0.111.0")]
ali3nsystems.lib-atmokinetic_assets / Code/Camera/CameraController.Weather.WaterLine.cs
Game library
namespace AtmokineticAssets;

public sealed partial class CameraController : BasePostProcess<CameraController>, Component.ExecuteInEditor
{
	public GameObject WaterLineCameraGameObject;
	public CameraComponent WaterLineCameraComponent;
	public Texture WaterLineTexture;

	private void CheckWaterSubmersion()
	{
		var corners = GetCameraNearPlaneCorners();
		if (corners == null)
		{
			IsCameraSubmerged = false;
			IsCameraFullySubmerged = false;
			if (WaterLineCameraGameObject.IsValid())
			{
				WaterController?.DestroyWaterLineMesh();
				DisableWaterLineRendering();
			}
			return;
		}

		// Cached by EnvironmentController on awake - no per-frame scene scan.
		var waterControllers = EnvironmentController.CachedWaterControllers;
		int maxSubmergedCount = 0;
		WaterController activeWater = null;

		foreach (var water in waterControllers)
		{
			if (!water.IsValid()) continue;

			int submergedCount = CountCameraSubmergedCorners(corners, water);
			if (submergedCount > maxSubmergedCount)
			{
				maxSubmergedCount = submergedCount;
				activeWater = water;
			}
		}

		bool WasCameraFullySubmerged = IsCameraFullySubmerged;
		IsCameraSubmerged = maxSubmergedCount >= 1;

		// Use offset check for fully Submerged (only applies when already in water)
		int fullySubmergedCount = activeWater != null ? CountCameraFullySubmergedCorners(corners, activeWater) : 0;
		bool isNowFullySubmerged = fullySubmergedCount == 4;
		bool isNowPartiallySubmerged = IsCameraSubmerged && !isNowFullySubmerged;

		// Handle water controller change
		if (activeWater != WaterController)
		{
			WaterController?.DestroyWaterLineMesh();
			WaterController = activeWater;
		}

		// Water line cube (only when partially Submerged)
		if (isNowPartiallySubmerged && !WaterLineCameraGameObject.IsValid())
		{
			WaterController?.CreateWaterLineMesh();
			ActivateWaterLineRendering();
		}

		else if (!isNowPartiallySubmerged && WaterLineCameraGameObject.IsValid())
		{
			WaterController?.DestroyWaterLineMesh();
			DisableWaterLineRendering();
		}

		// Delay IsCameraFullySubmerged off by one frame to prevent flash
		if (WasCameraFullySubmerged && isNowPartiallySubmerged)
		{
			if (_transitioningFromFullySubmerged)
			{
				IsCameraFullySubmerged = false;
				_transitioningFromFullySubmerged = false;
			}
			else
			{
				IsCameraFullySubmerged = true;
				_transitioningFromFullySubmerged = true;
			}
		}
		else
		{
			IsCameraFullySubmerged = isNowFullySubmerged;
			_transitioningFromFullySubmerged = false;
		}
	}

	public void WaterLineShaderAttributes()
	{
		Attributes.Set("Water Line Size", WaterController.WaterLineSize);
		Attributes.Set("Water Line Blur", WaterController.WaterLineBlur);
		Attributes.Set("Water Line Color", WaterController.WaterLineColor);
	}

    private void ActivateWaterLineRendering()
	{
		CameraComponent.RenderExcludeTags.Add("water_line_mesh");

		WaterLineCameraGameObject = new GameObject { Name = "Water Line Camera", Parent = GameObject };
		// Local-only render helper - never include it in network snapshots.
		WaterLineCameraGameObject.Flags |= GameObjectFlags.NotNetworked;
		WaterLineCameraGameObject.NetworkMode = NetworkMode.Never;
		WaterLineCameraComponent = WaterLineCameraGameObject.AddComponent<CameraComponent>();
		WaterLineCameraComponent.BackgroundColor = Color.Black;
		WaterLineCameraComponent.IsMainCamera = false;
		WaterLineCameraComponent.FovAxis = CameraComponent.FovAxis;
		WaterLineCameraComponent.FieldOfView = CameraComponent.FieldOfView;
		WaterLineCameraComponent.ZNear = CameraComponent.ZNear;
		WaterLineCameraComponent.ZFar = CameraComponent.ZFar;
		WaterLineCameraComponent.Priority = CameraComponent.Priority + 1;
		WaterLineCameraComponent.Orthographic = CameraComponent.Orthographic;
		WaterLineCameraComponent.OrthographicHeight = CameraComponent.OrthographicHeight;
		WaterLineCameraComponent.TargetEye = CameraComponent.TargetEye;
		WaterLineCameraComponent.Viewport = CameraComponent.Viewport;
		WaterLineCameraComponent.RenderTags.Add("water_line_mesh");
		WaterLineCameraComponent.RenderExcludeTags.RemoveAll();
		WaterLineCameraComponent.EnablePostProcessing = false;
	}

	private void RenderWaterLineTexture()
	{
		WaterLineTexture = Texture.CreateRenderTarget("WaterLineRenderTexture", ImageFormat.A8, Screen.Size, WaterLineTexture);
		WaterLineCameraComponent.RenderTarget = WaterLineTexture;
		Attributes.Set("WaterLineTexture", WaterLineTexture);
	}

	private void DisableWaterLineRendering()
	{
		WaterLineCameraGameObject.Destroy();
		WaterLineCameraGameObject = null;
		WaterLineCameraComponent = null;

		WaterLineTexture.Dispose();
		WaterLineTexture = null;

		CameraComponent.RenderExcludeTags.Remove("water_line_mesh");
	}
}
Debug: View Raw JSON Response
{
    "TotalCount": 68,
    "Files": [
        {
            "Ident": "ali3nsystems.lib-atmokinetic_assets",
            "Path": "Camera/CameraController.cs",
            "FileName": "CameraController.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 338612,
            "IsPrivate": false,
            "Code": "namespace AtmokineticAssets;\r\n\r\npublic sealed partial class CameraController : BasePostProcess<CameraController>\r\n{\r\n\tpublic Material material {get;set;} = Material.Load(\"materials/mat-camera_weather.vmat\");\r\n\tpublic bool IsCameraSubmerged { get; private set; }\r\n\tpublic bool IsCameraFullySubmerged { get; private set; }\r\n\tpublic float TimeSinceCameraLastExitedWater;\r\n\tpublic bool WasCameraFullySubmerged;\r\n\tprivate bool _transitioningFromFullySubmerged;\r\n\tpublic WaterController WaterController;\r\n\tpublic CameraComponent CameraComponent;\r\n\r\n\tprotected override void OnAwake()\r\n\t{\r\n\t\tCameraComponent = GameObject.GetComponent<CameraComponent>();\r\n\t}\r\n\r\n\tprotected override void OnUpdate()\r\n\t{\r\n\t\tCheckWaterSubmersion();\r\n\t\tUpdateWaterDrips();\r\n\t}\r\n\r\n\tpublic override void Render()\r\n\t{\r\n\t\tif (WaterLineCameraComponent.IsValid())\r\n\t\t{\r\n\t\t\tRenderWaterLineTexture();\r\n\t\t}\r\n\r\n\t\tif (WaterController.IsValid())\r\n\t\t{\r\n\t\t\tUnderWaterShaderAttributes();\r\n\t\t\t// WaterDripsShaderAttributes();\r\n\t\t\tWaterLineShaderAttributes();\r\n\t\t}\r\n\r\n\t\tWaterDripsFadeShaderAttribute();\r\n\t\tAttributes.Set(\"Is Camera Submerged\", (IsCameraSubmerged && !IsCameraFullySubmerged) ? 1f : 0f);\r\n\t\tAttributes.Set(\"Is Fully Submerged\", IsCameraFullySubmerged ? 1f : 0f);\r\n\r\n\t\tBlit(BlitMode.WithBackbuffer(material, Stage.BeforePostProcess, 100), \"Water Post Process\");\r\n\t}\r\n}\r\n"
        },
        {
            "Ident": "ali3nsystems.lib-atmokinetic_assets",
            "Path": "Camera/CameraController.Weather.WaterLine.cs",
            "FileName": "CameraController.Weather.WaterLine.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 338612,
            "IsPrivate": false,
            "Code": "namespace AtmokineticAssets;\r\n\r\npublic sealed partial class CameraController : BasePostProcess<CameraController>, Component.ExecuteInEditor\r\n{\r\n\tpublic GameObject WaterLineCameraGameObject;\r\n\tpublic CameraComponent WaterLineCameraComponent;\r\n\tpublic Texture WaterLineTexture;\r\n\r\n\tprivate void CheckWaterSubmersion()\r\n\t{\r\n\t\tvar corners = GetCameraNearPlaneCorners();\r\n\t\tif (corners == null)\r\n\t\t{\r\n\t\t\tIsCameraSubmerged = false;\r\n\t\t\tIsCameraFullySubmerged = false;\r\n\t\t\tif (WaterLineCameraGameObject.IsValid())\r\n\t\t\t{\r\n\t\t\t\tWaterController?.DestroyWaterLineMesh();\r\n\t\t\t\tDisableWaterLineRendering();\r\n\t\t\t}\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t// Cached by EnvironmentController on awake - no per-frame scene scan.\r\n\t\tvar waterControllers = EnvironmentController.CachedWaterControllers;\r\n\t\tint maxSubmergedCount = 0;\r\n\t\tWaterController activeWater = null;\r\n\r\n\t\tforeach (var water in waterControllers)\r\n\t\t{\r\n\t\t\tif (!water.IsValid()) continue;\r\n\r\n\t\t\tint submergedCount = CountCameraSubmergedCorners(corners, water);\r\n\t\t\tif (submergedCount > maxSubmergedCount)\r\n\t\t\t{\r\n\t\t\t\tmaxSubmergedCount = submergedCount;\r\n\t\t\t\tactiveWater = water;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tbool WasCameraFullySubmerged = IsCameraFullySubmerged;\r\n\t\tIsCameraSubmerged = maxSubmergedCount >= 1;\r\n\r\n\t\t// Use offset check for fully Submerged (only applies when already in water)\r\n\t\tint fullySubmergedCount = activeWater != null ? CountCameraFullySubmergedCorners(corners, activeWater) : 0;\r\n\t\tbool isNowFullySubmerged = fullySubmergedCount == 4;\r\n\t\tbool isNowPartiallySubmerged = IsCameraSubmerged && !isNowFullySubmerged;\r\n\r\n\t\t// Handle water controller change\r\n\t\tif (activeWater != WaterController)\r\n\t\t{\r\n\t\t\tWaterController?.DestroyWaterLineMesh();\r\n\t\t\tWaterController = activeWater;\r\n\t\t}\r\n\r\n\t\t// Water line cube (only when partially Submerged)\r\n\t\tif (isNowPartiallySubmerged && !WaterLineCameraGameObject.IsValid())\r\n\t\t{\r\n\t\t\tWaterController?.CreateWaterLineMesh();\r\n\t\t\tActivateWaterLineRendering();\r\n\t\t}\r\n\r\n\t\telse if (!isNowPartiallySubmerged && WaterLineCameraGameObject.IsValid())\r\n\t\t{\r\n\t\t\tWaterController?.DestroyWaterLineMesh();\r\n\t\t\tDisableWaterLineRendering();\r\n\t\t}\r\n\r\n\t\t// Delay IsCameraFullySubmerged off by one frame to prevent flash\r\n\t\tif (WasCameraFullySubmerged && isNowPartiallySubmerged)\r\n\t\t{\r\n\t\t\tif (_transitioningFromFullySubmerged)\r\n\t\t\t{\r\n\t\t\t\tIsCameraFullySubmerged = false;\r\n\t\t\t\t_transitioningFromFullySubmerged = false;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tIsCameraFullySubmerged = true;\r\n\t\t\t\t_transitioningFromFullySubmerged = true;\r\n\t\t\t}\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tIsCameraFullySubmerged = isNowFullySubmerged;\r\n\t\t\t_transitioningFromFullySubmerged = false;\r\n\t\t}\r\n\t}\r\n\r\n\tpublic void WaterLineShaderAttributes()\r\n\t{\r\n\t\tAttributes.Set(\"Water Line Size\", WaterController.WaterLineSize);\r\n\t\tAttributes.Set(\"Water Line Blur\", WaterController.WaterLineBlur);\r\n\t\tAttributes.Set(\"Water Line Color\", WaterController.WaterLineColor);\r\n\t}\r\n\r\n    private void ActivateWaterLineRendering()\r\n\t{\r\n\t\tCameraComponent.RenderExcludeTags.Add(\"water_line_mesh\");\r\n\r\n\t\tWaterLineCameraGameObject = new GameObject { Name = \"Water Line Camera\", Parent = GameObject };\r\n\t\t// Local-only render helper - never include it in network snapshots.\r\n\t\tWaterLineCameraGameObject.Flags |= GameObjectFlags.NotNetworked;\r\n\t\tWaterLineCameraGameObject.NetworkMode = NetworkMode.Never;\r\n\t\tWaterLineCameraComponent = WaterLineCameraGameObject.AddComponent<CameraComponent>();\r\n\t\tWaterLineCameraComponent.BackgroundColor = Color.Black;\r\n\t\tWaterLineCameraComponent.IsMainCamera = false;\r\n\t\tWaterLineCameraComponent.FovAxis = CameraComponent.FovAxis;\r\n\t\tWaterLineCameraComponent.FieldOfView = CameraComponent.FieldOfView;\r\n\t\tWaterLineCameraComponent.ZNear = CameraComponent.ZNear;\r\n\t\tWaterLineCameraComponent.ZFar = CameraComponent.ZFar;\r\n\t\tWaterLineCameraComponent.Priority = CameraComponent.Priority + 1;\r\n\t\tWaterLineCameraComponent.Orthographic = CameraComponent.Orthographic;\r\n\t\tWaterLineCameraComponent.OrthographicHeight = CameraComponent.OrthographicHeight;\r\n\t\tWaterLineCameraComponent.TargetEye = CameraComponent.TargetEye;\r\n\t\tWaterLineCameraComponent.Viewport = CameraComponent.Viewport;\r\n\t\tWaterLineCameraComponent.RenderTags.Add(\"water_line_mesh\");\r\n\t\tWaterLineCameraComponent.RenderExcludeTags.RemoveAll();\r\n\t\tWaterLineCameraComponent.EnablePostProcessing = false;\r\n\t}\r\n\r\n\tprivate void RenderWaterLineTexture()\r\n\t{\r\n\t\tWaterLineTexture = Texture.CreateRenderTarget(\"WaterLineRenderTexture\", ImageFormat.A8, Screen.Size, WaterLineTexture);\r\n\t\tWaterLineCameraComponent.RenderTarget = WaterLineTexture;\r\n\t\tAttributes.Set(\"WaterLineTexture\", WaterLineTexture);\r\n\t}\r\n\r\n\tprivate void DisableWaterLineRendering()\r\n\t{\r\n\t\tWaterLineCameraGameObject.Destroy();\r\n\t\tWaterLineCameraGameObject = null;\r\n\t\tWaterLineCameraComponent = null;\r\n\r\n\t\tWaterLineTexture.Dispose();\r\n\t\tWaterLineTexture = null;\r\n\r\n\t\tCameraComponent.RenderExcludeTags.Remove(\"water_line_mesh\");\r\n\t}\r\n}"
        },
        {
            "Ident": "ali3nsystems.lib-atmokinetic_assets",
            "Path": "Code/Assembly.cs",
            "FileName": "Assembly.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 338612,
            "IsPrivate": false,
            "Code": "global using Sandbox;\r\nglobal using Sandbox.Rendering;\r\nglobal using System;\r\nglobal using System.Collections.Generic;\r\nglobal using System.Runtime.InteropServices;\r\nglobal using System.Text.RegularExpressions;\r\nglobal using System.Linq;\r\n"
        },
        {
            "Ident": "ali3nsystems.lib-atmokinetic_assets",
            "Path": "Code/Camera/CameraController.Weather.UnderWater.cs",
            "FileName": "CameraController.Weather.UnderWater.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 338612,
            "IsPrivate": false,
            "Code": "namespace AtmokineticAssets;\r\n\r\npublic sealed partial class CameraController : BasePostProcess<CameraController>\r\n{\r\n    public void UnderWaterShaderAttributes()\r\n    {\r\n        Attributes.Set(\"Under Water Blur\", WaterController.UnderWaterBlur);\r\n        Attributes.Set(\"Under Water Color\", WaterController.UnderWaterColor);\r\n    }\t\r\n}"
        },
        {
            "Ident": "ali3nsystems.lib-atmokinetic_assets",
            "Path": "Code/Graphics/GraphicsSettings.TerrainSettings.cs",
            "FileName": "GraphicsSettings.TerrainSettings.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 338612,
            "IsPrivate": false,
            "Code": "namespace AtmokineticAssets;\r\n\r\npublic sealed partial class GraphicsSettings : Component, Component.ExecuteInEditor\r\n{\r\n    //Terrain Settings\r\n    public enum TerrainRenderingModeEnumeration\r\n    {\r\n        [Title(\"Parallax Occlusion Mapping\")]\r\n        ParallaxOcclusionMapping,\r\n        \r\n        [Title(\"Bump Offset\")]\r\n        BumpOffset,\r\n       \r\n        Standard\r\n    }\r\n\r\n    //[Property, Feature(\"Terrain\"), Title(\"Rendering\")]\r\n    public TerrainRenderingModeEnumeration TerrainRenderingMode { get; set; } = TerrainRenderingModeEnumeration.Standard;\r\n\r\n    public enum TerrainRenderingParallaxOcclusionMappingQualityEnumeration\r\n    {\r\n        Ultra,\r\n        High,\r\n        Medium,\r\n        Low\r\n    }\r\n\r\n    //[Property, Feature(\"Terrain\"), Title(\"Quality\")] \r\n    [ShowIf(nameof(TerrainRenderingMode), TerrainRenderingModeEnumeration.ParallaxOcclusionMapping)]\r\n    public TerrainRenderingParallaxOcclusionMappingQualityEnumeration TerrainRenderingParallaxOcclusionMapping { get; set; } = TerrainRenderingParallaxOcclusionMappingQualityEnumeration.Medium;\r\n\r\n    public enum TerrainRenderingBumpOffsetQualityEnumeration\r\n    {\r\n        Ultra,\r\n        High,\r\n        Medium,\r\n        Low\r\n    }\r\n   \r\n    //[Property, Feature(\"Terrain\"), Title(\"Quality\")] \r\n    [ShowIf(nameof(TerrainRenderingMode), TerrainRenderingModeEnumeration.BumpOffset)]\r\n    public TerrainRenderingParallaxOcclusionMappingQualityEnumeration TerrainRenderingBumpOffset { get; set; } = TerrainRenderingParallaxOcclusionMappingQualityEnumeration.Medium;\r\n\r\n\r\n    // public void ChangeTerrainSettings()\r\n    // {\r\n    //             // Terrain rendering mode: 0 = POM, 1 = Bump Offset, 2 = Standard\r\n    //     int terrainRenderingMode = TerrainRenderingMode switch\r\n    //     {\r\n    //         TerrainRenderingModeEnumeration.ParallaxOcclusionMapping => 0,\r\n    //         TerrainRenderingModeEnumeration.BumpOffset => 1,\r\n    //         TerrainRenderingModeEnumeration.Standard => 2,\r\n    //         _ => 1\r\n    //     };\r\n\r\n    //     // Quality: Ultra = 1, High = 2, Medium = 4, Low = 8\r\n    //     float quality;\r\n    //     switch ( TerrainRenderingMode )\r\n    //     {\r\n    //         case TerrainRenderingModeEnumeration.ParallaxOcclusionMapping:\r\n    //             quality = TerrainRenderingParallaxOcclusionMapping switch\r\n    //             {\r\n    //                 TerrainRenderingParallaxOcclusionMappingQualityEnumeration.Ultra => 1f,\r\n    //                 TerrainRenderingParallaxOcclusionMappingQualityEnumeration.High => 2f,\r\n    //                 TerrainRenderingParallaxOcclusionMappingQualityEnumeration.Medium => 4f,\r\n    //                 TerrainRenderingParallaxOcclusionMappingQualityEnumeration.Low => 8f,\r\n    //                 _ => 4f\r\n    //             };\r\n    //             break;\r\n\r\n    //         case TerrainRenderingModeEnumeration.BumpOffset:\r\n    //             quality = TerrainRenderingBumpOffset switch\r\n    //             {\r\n    //                 TerrainRenderingParallaxOcclusionMappingQualityEnumeration.Ultra => 1f,\r\n    //                 TerrainRenderingParallaxOcclusionMappingQualityEnumeration.High => 2f,\r\n    //                 TerrainRenderingParallaxOcclusionMappingQualityEnumeration.Medium => 4f,\r\n    //                 TerrainRenderingParallaxOcclusionMappingQualityEnumeration.Low => 8f,\r\n    //                 _ => 4f\r\n    //             };\r\n    //             break;\r\n\r\n    //         default:\r\n    //             quality = 4f;\r\n    //             break;\r\n    //     }\r\n\r\n    //     // Enumerate live \u2014 objects spawned after startup (e.g. baked water tiles) must be included.\r\n    //     foreach ( var sceneObject in Scene.SceneWorld.SceneObjects )\r\n    //     {\r\n    //         if ( sceneObject is null || !sceneObject.IsValid() ) continue;\r\n    //         sceneObject.Attributes.Set( \"D_Rendering\", terrainRenderingMode );\r\n    //         sceneObject.Attributes.Set( \"Quality\", quality );\r\n    //     }\r\n    // }\r\n}"
        },
        {
            "Ident": "ali3nsystems.lib-atmokinetic_assets",
            "Path": "Player/CharacterControllerUtilities.DisableSelfCollision.cs",
            "FileName": "CharacterControllerUtilities.DisableSelfCollision.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 338612,
            "IsPrivate": false,
            "Code": "using Sandbox.Physics;\r\n\r\nnamespace AtmokineticAssets;\r\n\r\n[Title( \"Atmokinetic Assets - Character Controller Utilities\" )]\r\n[Category( \"Atmokinetic Assets\" )]\r\n[Icon( \"badge\" )]\r\n\r\n\r\npublic sealed partial class CharacterControllerUtilities : Component, Component.INetworkSpawn\r\n{\r\n    /// <summary>\r\n    /// THIS IS PRIMARILY USED FOR THE MODELPHYISICS TO GENERATE WATER SIMULATIONS/RIPPLES\r\n    /// WITHOUT COLLIDING WITH THE CHARACTER CONTROLLER'S BOX/CAPSULE COLLIDER. \r\n    /// IT USES THE STEAMID AS AN IDENTIFIER.\r\n    /// </summary>\r\n\r\n    private string _appliedTag;\r\n    private ModelPhysics _modelPhysics;\r\n    private int _lastTaggedBodyCount = -1;\r\n\r\n    public void OnNetworkSpawn( Connection owner )\r\n    {\r\n        EnsurePhysics();\r\n        ApplyTag( owner );\r\n    }\r\n\r\n    protected override void OnStart()\r\n    {\r\n        EnsurePhysics();\r\n\r\n        // Fallback for non-networked scenes: tag with the local connection\r\n        ApplyTag( Network.Owner ?? Connection.Local );\r\n    }\r\n\r\n    // Create a ModelPhysics from the player's SkinnedModelRenderer if one is\r\n    // missing, so the collision bodies this component tags actually exist.\r\n    private void EnsurePhysics()\r\n    {\r\n        if ( GameObject.GetComponentInChildren<ModelPhysics>( true ).IsValid() )\r\n            return;\r\n\r\n        var renderer = GameObject.GetComponentInChildren<SkinnedModelRenderer>( true );\r\n        if ( !renderer.IsValid() || renderer.Model is null )\r\n            return;\r\n\r\n        var physics = renderer.GameObject.AddComponent<ModelPhysics>();\r\n        physics.Renderer = renderer;\r\n        physics.Model = renderer.Model;\r\n        physics.MotionEnabled = false;\r\n    }\r\n\r\n    protected override void OnUpdate()\r\n    {\r\n        // ModelPhysics creates its ragdoll bone objects lazily (and rebuilds them on\r\n        // model reload). Instead of walking every child collider each tick, watch the\r\n        // body count - it only changes when the ragdoll is (re)built.\r\n        if ( string.IsNullOrEmpty( _appliedTag ) )\r\n            return;\r\n\r\n        if ( !_modelPhysics.IsValid() )\r\n            _modelPhysics = GameObject.GetComponentInChildren<ModelPhysics>( true );\r\n\r\n        int bodyCount = _modelPhysics.IsValid() ? _modelPhysics.Bodies.Count : 0;\r\n        if ( bodyCount == _lastTaggedBodyCount )\r\n            return;\r\n\r\n        _lastTaggedBodyCount = bodyCount;\r\n        TagColliders( _appliedTag );\r\n    }\r\n\r\n    private void ApplyTag( Connection connection )\r\n    {\r\n        if ( connection is null )\r\n            return;\r\n\r\n        var tag = connection.SteamId.ToString();\r\n\r\n        if ( _appliedTag == tag )\r\n            return;\r\n\r\n        // Clean up a previously applied id (e.g. after ownership transfer)\r\n        if ( !string.IsNullOrEmpty( _appliedTag ) )\r\n            GameObject.Tags.Remove( _appliedTag );\r\n\r\n        // The tag itself replicates over the network (it's GameObject state), so a\r\n        // joining client can see it already applied from the snapshot - but the\r\n        // collision-ignore rule lives in this machine's own PhysicsWorld, which is\r\n        // NOT networked. So only skip re-adding the tag; always (re)register the\r\n        // rule locally, or clients would collide with their own ragdoll forever.\r\n        if ( !GameObject.Tags.Has( tag ) )\r\n            GameObject.Tags.Add( tag );\r\n\r\n        IgnoreSelfCollision( tag );\r\n        TagColliders( tag );\r\n\r\n        _appliedTag = tag;\r\n    }\r\n\r\n    // Add a rule so any two shapes both carrying this exact Steam ID tag don't\r\n    // collide. Only this player's own colliders carry it, so its ragdoll ignores\r\n    // its own body while still colliding with other players (whose tag differs).\r\n    private void IgnoreSelfCollision( string tag )\r\n    {\r\n        var rules = Scene.PhysicsWorld?.CollisionRules;\r\n        if ( rules is null )\r\n            return;\r\n\r\n        rules.Pairs[new CollisionRules.Pair( tag, tag )] = CollisionRules.Result.Ignore;\r\n    }\r\n\r\n    // Stamp the Steam ID tag onto every collider under the player. Shapes take\r\n    // their tags from their GameObject, and ragdoll bone objects don't inherit\r\n    // the root's tags, so each one has to be tagged directly.\r\n    private void TagColliders( string tag )\r\n    {\r\n        foreach ( var collider in GameObject.GetComponentsInChildren<Collider>( true ) )\r\n        {\r\n            if ( !collider.GameObject.Tags.Has( tag ) )\r\n                collider.GameObject.Tags.Add( tag );\r\n        }\r\n    }\r\n\r\n}\r\n"
        },
        {
            "Ident": "ali3nsystems.lib-atmokinetic_assets",
            "Path": "Water/Modes/Tiles/WaterController.WaterTiles.cs",
            "FileName": "WaterController.WaterTiles.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 338612,
            "IsPrivate": false,
            "Code": "namespace AtmokineticAssets;\r\n\r\npublic sealed partial class WaterController: Component, Component.ITriggerListener\r\n{\r\n\t\r\n\t\r\n\t[Property, Title(\"Tile Model\"), Feature(\"General\"), Group(\"Properties\"), ShowIf( nameof(WaterMode), WaterModeEnumeration.Tiles )]\r\n\tpublic Model WaterTileModel { get; set; } = Model.Load(\"models/mdl-water_tile.vmdl\");\r\n\t\r\n\t[Property, Title(\"Tile Size (cm)\"), Feature(\"General\"), Group(\"Properties\"), ShowIf( nameof(WaterMode), WaterModeEnumeration.Tiles )]\r\n\tpublic float WaterTileSize\r\n\t{\r\n\t\tget;\r\n\t\tset\r\n\t\t{\r\n\t\t\tfield = ResizeWaterTileBounds( field, value );\r\n\t\t}\r\n\t} = 500f;\r\n\t\r\n\t[Property, Title(\"Bounds Maximum (cm)\"), Feature(\"General\"), Group(\"Properties\"), ShowIf( nameof(WaterMode), WaterModeEnumeration.Tiles )]\r\n\tpublic Vector3 WaterTileBoundsMaximum { get; set; } = new( 500, 500, 500 );\r\n\r\n\t[Property, Title(\"Bounds Minimum (cm)\"), Feature(\"General\"), Group(\"Properties\"), ShowIf( nameof(WaterMode), WaterModeEnumeration.Tiles )]\r\n\tpublic Vector3 WaterTileBoundsMinimum { get; set; } = new( -500, -500, -500 );\r\n\r\n\t[Button(\"Generate Water Tiles\"), Feature(\"General\"), Group(\"Properties\"), ShowIf( nameof(WaterMode), WaterModeEnumeration.Tiles )]\r\n\tpublic void GenerateWaterTiles()\r\n\t{\r\n\t\tBake();\r\n\t\tWaterSurfaceAttributes();\r\n\t}\r\n\r\n\t[Property, Title(\"Enable Gizmo\"),Feature( \"General\" ), ShowIf( nameof(WaterMode), WaterModeEnumeration.Tiles)]\r\n\tpublic bool EnableWaterTileGizmo { get; set; } = true;\r\n\tprivate readonly Dictionary<(int x, int y), (GameObject go, ModelRenderer renderer)> _tiles = [];\r\n\tprivate Vector2 _lastSnappedPosition = new( float.MinValue, float.MinValue );\r\n\tprivate BoxCollider _waterCollider;\r\n\tprivate bool _waterLineActive;\r\n\tprivate readonly Dictionary<(int x, int y), GameObject> _waterLineChildren = [];\r\n\r\n\t// True once the component has started \u2014 gates editor-only bounds rescaling so it\r\n\t// never fires during scene deserialization (which would clobber the saved bounds).\r\n\tprivate bool _tilesStarted;\r\n\r\n\tpublic float SubmersionOffset => -(WaterRipplesDisplacementA + WaterRipplesDisplacementB);\r\n\r\n\t// World-adjusted snapped bounds (inches). Internal counterparts to the public\r\n\t// WaterTileBounds* props, which must NEVER be written by code.\r\n\tprivate Vector3 _waterTileBoundsMinimum;\r\n\tprivate Vector3 _waterTileBoundsMaximum;\r\n\r\n\t// Integer tile-grid indices, computed once in CalculateBounds (half-open range).\r\n\tprivate int _tileMinX, _tileMaxX, _tileMinY, _tileMaxY;\r\n\r\n\t// Cached bounds in world space (inches) \u2014 derived from the integer indices above.\r\n\tprivate Vector2 _boundsMin;\r\n\tprivate Vector2 _boundsMax;\r\n\tprivate float _waterZ;\r\n\r\n\t// Tile size in cm (all meshes are same size)\r\n\r\n\tpublic IEnumerable<ModelRenderer> GetTileRenderers()\r\n\t{\r\n\t\tforeach ( var kvp in _tiles )\r\n\t\t{\r\n\t\t\tif ( kvp.Value.renderer.IsValid() )\r\n\t\t\t\tyield return kvp.Value.renderer;\r\n\t\t}\r\n\t}\r\n\r\n\tpublic void Bake()\r\n\t{\r\n\t\tCalculateRingLayout();\r\n\t\tDestroyAllTiles();\r\n\t\tCalculateBounds();\r\n\t\tCreateWaterCollider();\r\n\t\tBakeTiles();\r\n\t}\r\n\r\n\tpublic float GetWaterSurfaceZ() => _waterZ;\r\n\tpublic float GetWaterSurfaceZWithOffset() => _waterZ + SubmersionOffset;\r\n\r\n\tpublic BBox GetWorldBounds()\r\n\t{\r\n\t\tfloat minZ = WorldPosition.z + UnitConversion.CentimetersToInches( WaterTileBoundsMinimum.z );\r\n\t\treturn new BBox(\r\n\t\t\tnew Vector3( _boundsMin.x, _boundsMin.y, minZ ),\r\n\t\t\tnew Vector3( _boundsMax.x, _boundsMax.y, _waterZ ) );\r\n\t}\r\n\r\n\t// True when the whole water bounds are outside the camera frustum, i.e. every tile is\r\n\t// culled. One frustum-vs-BBox test per frame, independent of tile count.\r\n\t// (SceneObject.RenderingEnabled can't be used \u2014 it's a toggle you set, not a per-frame\r\n\t// cull result, and the engine doesn't expose \"was culled last frame\" publicly. Frustum\r\n\t// is the only reliable option; it misses occlusion, which isn't cheaply queryable.)\r\n\tpublic bool AreAllTilesCulled()\r\n\t{\r\n\t\tvar camera = Scene.Camera;\r\n\t\tif ( !camera.IsValid() )\r\n\t\t\treturn true;\r\n\r\n\t\tvar frustum = camera.GetFrustum( new Rect( 0, 0, Screen.Width, Screen.Height ) );\r\n\t\treturn !frustum.IsInside( GetWorldBounds(), partially: true );\r\n\t}\r\n\r\n\tpublic bool IsPointInWaterVolume( Vector3 point )\r\n\t{\r\n\t\tvar b = GetWorldBounds();\r\n\t\treturn point.x >= b.Mins.x && point.x <= b.Maxs.x &&\r\n\t\t\t   point.y >= b.Mins.y && point.y <= b.Maxs.y &&\r\n\t\t\t   point.z < GetWaterSurfaceZ() + ripples.Count && point.z >= b.Mins.z;\r\n\t}\r\n\r\n\tpublic bool IsPointFullySubmerged( Vector3 point )\r\n\t{\r\n\t\tvar b = GetWorldBounds();\r\n\t\treturn point.x >= b.Mins.x && point.x <= b.Maxs.x &&\r\n\t\t\t   point.y >= b.Mins.y && point.y <= b.Maxs.y &&\r\n\t\t\t   point.z < GetWaterSurfaceZWithOffset() && point.z >= b.Mins.z;\r\n\t}\r\n\r\n\t// Body group layout convention on the \"subdivision\" body group:\r\n\t//   0        : water-line variant of ring 0 (rendered only by the water-line camera)\r\n\t//   1        : ring 0 (center, 2x2 quadrants)\r\n\t//   2..n-2   : an end/corner pair per ring (subdivided_N_end, subdivided_N_corner)\r\n\t//   n-1      : the last subdivision (far filler, no variants)\r\n\t// MaxRings and the far index are derived from the model in CalculateRingLayout.\r\n\tprivate int MaxRings { get; set; } = 4;\r\n\tprivate int FarBodyGroupIndex { get; set; } = 8;\r\n\r\n\t// Derive the ring count from the model's \"subdivision\" body group, so adding or\r\n\t// removing ring meshes in the model needs no code change.\r\n\tprivate void CalculateRingLayout()\r\n\t{\r\n\t\tvar subdivisionPart = WaterTileModel?.Parts.All\r\n\t\t\t.FirstOrDefault( p => p.Name == \"subdivision\" );\r\n\r\n\t\t// Minimum viable layout: waterline + ring 0 + one end/corner pair + far.\r\n\t\tif ( subdivisionPart is null || subdivisionPart.Choices.Count < 5 )\r\n\t\t\treturn;\r\n\r\n\t\tint choiceCount = subdivisionPart.Choices.Count;\r\n\r\n\t\tif ( ( choiceCount - 3 ) % 2 != 0 )\r\n\t\t\tLog.Warning( $\"Water tile model '{WaterTileModel.ResourceName}' has {choiceCount} subdivision choices - expected waterline + ring0 + end/corner pairs + far. Ring layout may be wrong.\" );\r\n\r\n\t\tFarBodyGroupIndex = choiceCount - 1;\r\n\t\tMaxRings = ( choiceCount - 3 ) / 2 + 1;\r\n\t}\r\n\r\n\tprivate enum TileType\r\n\t{\r\n\t\tEnd,      // Outline side\r\n\t\tCorner    // Outline corner\r\n\t}\r\n\r\n\tprivate int GetBodyGroupForRing( int ringLevel, TileType tileType )\r\n\t{\r\n\t\tif ( ringLevel == 0 )\r\n\t\t\treturn 1;\r\n\t\tif ( ringLevel >= MaxRings )\r\n\t\t\treturn FarBodyGroupIndex;\r\n\r\n\t\tint baseIndex = ( ringLevel - 1 ) * 2 + 2;\r\n\t\treturn tileType == TileType.Corner ? baseIndex + 1 : baseIndex;\r\n\t}\r\n\r\n\tprivate void UpdateRings()\r\n\t{\r\n\t\tif ( _tiles.Count == 0 )\r\n\t\t\treturn;\r\n\r\n\t\tvar camera = Scene.Camera;\r\n\t\tif ( camera == null )\r\n\t\t\treturn;\r\n\r\n\t\t// Half-tile bias shifts ring transitions to tile centers instead of tile edges.\r\n\t\t// Snap the camera in box-local space (relative to the GameObject origin) to match the tile layout.\r\n\t\tfloat tileSizeInches = UnitConversion.CentimetersToInches( WaterTileSize );\r\n\t\tfloat halfTile = tileSizeInches * 0.5f;\r\n\t\tfloat localCamX = camera.WorldPosition.x - WorldPosition.x;\r\n\t\tfloat localCamY = camera.WorldPosition.y - WorldPosition.y;\r\n\t\tfloat snappedX = MathF.Floor( ( localCamX - halfTile ) / tileSizeInches ) * tileSizeInches;\r\n\t\tfloat snappedY = MathF.Floor( ( localCamY - halfTile ) / tileSizeInches ) * tileSizeInches;\r\n\t\tVector2 snappedPos = new( snappedX, snappedY );\r\n\r\n\t\tif ( snappedPos != _lastSnappedPosition )\r\n\t\t{\r\n\t\t\t_lastSnappedPosition = snappedPos;\r\n\t\t\tUpdateTileBodyGroups( snappedPos );\r\n\t\t\tSyncWaterLineChildren();\r\n\t\t}\r\n\t}\r\n\r\n\tprivate void DestroyAllTiles()\r\n\t{\r\n\t\tforeach ( var kvp in _tiles )\r\n\t\t{\r\n\t\t\tif ( kvp.Value.go.IsValid() )\r\n\t\t\t\tkvp.Value.go.Destroy();\r\n\t\t}\r\n\t\t_tiles.Clear();\r\n\t\tMarkRippleRenderersDirty();\r\n\t}\r\n\r\n\tpublic void CreateWaterLineMesh()\r\n\t{\r\n\t\tif ( _waterLineActive ) return;\r\n\t\t_waterLineActive = true;\r\n\t\t// If tiles are already live, sync immediately; otherwise OnUpdate will handle it on next snap.\r\n\t\tif ( _tiles.Count > 0 )\r\n\t\t\tSyncWaterLineChildren();\r\n\t}\r\n\r\n\tpublic void DestroyWaterLineMesh()\r\n\t{\r\n\t\t_waterLineActive = false;\r\n\t\tforeach ( var kvp in _waterLineChildren )\r\n\t\t{\r\n\t\t\tif ( kvp.Value.IsValid() )\r\n\t\t\t\tkvp.Value.Destroy();\r\n\t\t}\r\n\t\t_waterLineChildren.Clear();\r\n\t\tMarkRippleRenderersDirty();\r\n\t}\r\n\r\n\tprivate void SyncWaterLineChildren()\r\n\t{\r\n\t\tif ( !_waterLineActive || WaterTileModel == null )\r\n\t\t\treturn;\r\n\r\n\t\tvar ringZeroKeys = new HashSet<(int x, int y)>();\r\n\t\tforeach ( var kvp in _tiles )\r\n\t\t{\r\n\t\t\tif ( kvp.Value.renderer.IsValid() && kvp.Value.renderer.GetBodyGroup( \"subdivision\" ) == 1 )\r\n\t\t\t\tringZeroKeys.Add( kvp.Key );\r\n\t\t}\r\n\r\n\t\tvar stale = new List<(int x, int y)>();\r\n\t\tforeach ( var kvp in _waterLineChildren )\r\n\t\t{\r\n\t\t\tif ( ringZeroKeys.Contains( kvp.Key ) )\r\n\t\t\t\tcontinue;\r\n\t\t\tif ( kvp.Value.IsValid() )\r\n\t\t\t\tkvp.Value.Destroy();\r\n\t\t\tstale.Add( kvp.Key );\r\n\t\t}\r\n\t\tforeach ( var k in stale )\r\n\t\t\t_waterLineChildren.Remove( k );\r\n\r\n\t\tif ( stale.Count > 0 )\r\n\t\t\tMarkRippleRenderersDirty();\r\n\r\n\t\tforeach ( var key in ringZeroKeys )\r\n\t\t{\r\n\t\t\tif ( _waterLineChildren.ContainsKey( key ) )\r\n\t\t\t\tcontinue;\r\n\t\t\tif ( !_tiles.TryGetValue( key, out var tile ) || !tile.go.IsValid() )\r\n\t\t\t\tcontinue;\r\n\r\n\t\t\tvar childGo = new GameObject( true, \"waterline\" ) { Parent = tile.go };\r\n\t\t\t// Local-only render mesh - never include it in network snapshots.\r\n\t\t\tchildGo.Flags |= GameObjectFlags.NotNetworked;\r\n\t\t\tchildGo.NetworkMode = NetworkMode.Never;\r\n\t\t\tchildGo.Tags.Add( \"water_line_mesh\" );\r\n\r\n\t\t\tvar r = childGo.Components.Create<ModelRenderer>();\r\n\t\t\tr.Model = WaterTileModel;\r\n\r\n\t\t\tr.RenderType = ModelRenderer.ShadowRenderType.Off;\r\n\t\t\tr.SceneObject.Attributes.Set( \"BoundsMin\", _boundsMin );\r\n\t\t\tr.SceneObject.Attributes.Set( \"BoundsMax\", _boundsMax );\r\n\t\t\t\r\n\t\t\tr.SceneObject.Attributes.Set( \"WaterRipplesTextureA\", WaterRipplesTextureA );\r\n\t\t\tr.SceneObject.Attributes.Set( \"Water Ripples Scale A\", WaterRipplesScaleA );\r\n\t\t\tr.SceneObject.Attributes.Set( \"Water Ripples Rotation A\", WaterRipplesRotationA );\r\n\t\t\tr.SceneObject.Attributes.Set( \"Water Ripples Speed A\", WaterRipplesSpeedA );\r\n\t\t\tr.SceneObject.Attributes.Set( \"Water Ripples Displacement A\", WaterRipplesDisplacementA );\r\n\r\n\t\t\tr.SceneObject.Attributes.Set( \"WaterRipplesTextureB\", WaterRipplesTextureB );\r\n\t\t\tr.SceneObject.Attributes.Set( \"Water Ripples Scale B\", WaterRipplesScaleB );\r\n\t\t\tr.SceneObject.Attributes.Set( \"Water Ripples Rotation B\", WaterRipplesRotationB );\r\n\t\t\tr.SceneObject.Attributes.Set( \"Water Ripples Speed B\", WaterRipplesSpeedB );\r\n\t\t\tr.SceneObject.Attributes.Set( \"Water Ripples Displacement B\", WaterRipplesDisplacementB );\r\n\r\n\r\n\t\t\tr.Attributes.SetCombo( \"D_TRANSLUCENT\", 0 );\r\n\t\t\tr.Attributes.SetCombo( \"D_Enable_Water_Line\", 1 );\r\n\t\t\tr.SetBodyGroup( \"subdivision\", 0 );\r\n\r\n\t\t\t_waterLineChildren[key] = childGo;\r\n\t\t\tMarkRippleRenderersDirty();\r\n\t\t}\r\n\t}\r\n\r\n\tpublic void OnTriggerEnter( Collider other )\r\n\t{\r\n\t\tif ( !other.GameObject.Tags.Has( \"particles\" ) )\r\n\t\t\treturn;\r\n\r\n\t\tother.GameObject.Destroy();\r\n\t}\r\n\r\n\tpublic void OnTriggerExit( Collider other )\r\n\t{\r\n\t}\r\n\r\n\tprivate void CreateWaterCollider()\r\n\t{\r\n\t\t// Only create the trigger collider at runtime, not while editing the scene.\r\n\t\tif ( Scene.IsEditor )\r\n\t\t\treturn;\r\n\r\n\t\t// Use the snapped tile region so the trigger matches the spawned tiles.\r\n\t\t// _waterTileBounds* are world-space; convert to local for the (parented) collider.\r\n\t\tVector3 localMin = _waterTileBoundsMinimum - WorldPosition;\r\n\t\tVector3 localMax = _waterTileBoundsMaximum - WorldPosition;\r\n\r\n\t\tVector3 center = ( localMin + localMax ) * 0.5f;\r\n\t\tVector3 size = localMax - localMin;\r\n\r\n\t\t_waterCollider ??= Components.Get<BoxCollider>( FindMode.InSelf ) ?? Components.Create<BoxCollider>();\r\n\t\t_waterCollider.Flags = ComponentFlags.Hidden;\r\n\t\t_waterCollider.Center = center;\r\n\t\t_waterCollider.Scale = size;\r\n\t\t_waterCollider.IsTrigger = true;\r\n\t\t_waterCollider.Static = true;\r\n\t}\r\n\r\n\tprivate void CalculateBounds()\r\n\t{\r\n\t\tvar minInches = UnitConversion.CentimetersToInches( WaterTileBoundsMinimum );\r\n\t\tvar maxInches = UnitConversion.CentimetersToInches( WaterTileBoundsMaximum );\r\n\r\n\t\tfloat tileSizeInches = UnitConversion.CentimetersToInches( WaterTileSize );\r\n\r\n\t\t// Snap the extent to the tile grid in LOCAL space (relative to the GameObject origin).\r\n\t\t// Tiles are anchored to the box, so they fill it exactly and follow the GameObject smoothly.\r\n\t\t_tileMinX = (int)MathF.Floor( MathF.Round( minInches.x / tileSizeInches, 4 ) );\r\n\t\t_tileMinY = (int)MathF.Floor( MathF.Round( minInches.y / tileSizeInches, 4 ) );\r\n\t\t_tileMaxX = (int)MathF.Ceiling( MathF.Round( maxInches.x / tileSizeInches, 4 ) );\r\n\t\t_tileMaxY = (int)MathF.Ceiling( MathF.Round( maxInches.y / tileSizeInches, 4 ) );\r\n\r\n\t\t// Local-space snapped corners.\r\n\t\tfloat minLocalX = _tileMinX * tileSizeInches;\r\n\t\tfloat minLocalY = _tileMinY * tileSizeInches;\r\n\t\tfloat maxLocalX = _tileMaxX * tileSizeInches;\r\n\t\tfloat maxLocalY = _tileMaxY * tileSizeInches;\r\n\r\n\t\t// World-space bounds (for collider-independent consumers, shader clipping, point queries).\r\n\t\t_waterTileBoundsMinimum = new Vector3( WorldPosition.x + minLocalX, WorldPosition.y + minLocalY, WorldPosition.z + minInches.z );\r\n\t\t_waterTileBoundsMaximum = new Vector3( WorldPosition.x + maxLocalX, WorldPosition.y + maxLocalY, WorldPosition.z + maxInches.z );\r\n\r\n\t\t_boundsMin = new Vector2( _waterTileBoundsMinimum.x, _waterTileBoundsMinimum.y );\r\n\t\t_boundsMax = new Vector2( _waterTileBoundsMaximum.x, _waterTileBoundsMaximum.y );\r\n\t\t_waterZ = _waterTileBoundsMaximum.z;\r\n\t}\r\n\r\n\tprivate void DiscoverBakedTiles()\r\n\t{\r\n\t\tCalculateRingLayout();\r\n\r\n\t\t// Rediscovered renderers need the surface attributes even if no property changed.\r\n\t\t_surfaceAttributesDirty = true;\r\n\t\tMarkRippleRenderersDirty();\r\n\r\n\r\n\t\tforeach ( var child in GameObject.Children.ToList() )\r\n\t\t{\r\n\t\t\tif ( !child.Name.StartsWith( \"tile_\" ) )\r\n\t\t\t\tcontinue;\r\n\r\n\t\t\tvar renderer = child.Components.Get<ModelRenderer>();\r\n\t\t\tif ( renderer == null )\r\n\t\t\t\tcontinue;\r\n\r\n\t\t\trenderer.SceneObject.Attributes.Set( \"BoundsMin\", _boundsMin );\r\n\t\t\trenderer.SceneObject.Attributes.Set( \"BoundsMax\", _boundsMax );\r\n\t\t\trenderer.Attributes.SetCombo( \"D_TRANSLUCENT\", 1 );\r\n\t\t\trenderer.Attributes.SetCombo( \"D_Enable_Water_Line\", 0 );\r\n\r\n\t\t\t// Parse tileX, tileY from name \"tile_X_Y\"\r\n\t\t\tvar parts = child.Name.Split( '_' );\r\n\t\t\tif ( parts.Length == 3 && int.TryParse( parts[1], out int tileX ) && int.TryParse( parts[2], out int tileY ) )\r\n\t\t\t{\r\n\t\t\t\t_tiles[(tileX, tileY)] = (child, renderer);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tprivate void BakeTiles()\r\n\t{\r\n\t\tif ( WaterTileModel == null )\r\n\t\t\treturn;\r\n\r\n\t\tfloat tileSizeInches = UnitConversion.CentimetersToInches( WaterTileSize );\r\n\t\tfloat halfTile = tileSizeInches * 0.5f;\r\n\r\n\t\tfor ( int tileX = _tileMinX; tileX < _tileMaxX; tileX++ )\r\n\t\t{\r\n\t\t\tfor ( int tileY = _tileMinY; tileY < _tileMaxY; tileY++ )\r\n\t\t\t{\r\n\t\t\t\t// Tile indices are local-grid; anchor positions to the GameObject origin.\r\n\t\t\t\tfloat worldX = WorldPosition.x + tileX * tileSizeInches;\r\n\t\t\t\tfloat worldY = WorldPosition.y + tileY * tileSizeInches;\r\n\r\n\t\t\t\tVector3 worldPosition = new Vector3( worldX + halfTile, worldY + halfTile, _waterZ );\r\n\t\t\t\tSpawnTile( worldPosition, tileX, tileY );\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tprivate void UpdateTileBodyGroups( Vector2 snappedPos )\r\n\t{\r\n\t\tfloat tileSizeInches = UnitConversion.CentimetersToInches( WaterTileSize );\r\n\r\n\t\t// First pass: calculate ring levels and find outermost ring\r\n\t\tvar tileData = new Dictionary<(int x, int y), (int ringLevel, int gridX, int gridY)>();\r\n\t\tint outermostRenderedRing = 0;\r\n\r\n\t\tforeach ( var kvp in _tiles )\r\n\t\t{\r\n\t\t\tint tileX = kvp.Key.x;\r\n\t\t\tint tileY = kvp.Key.y;\r\n\r\n\t\t\t// Box-local tile position (indices are local-grid; snappedPos is box-local).\r\n\t\t\tfloat localX = tileX * tileSizeInches;\r\n\t\t\tfloat localY = tileY * tileSizeInches;\r\n\r\n\t\t\tint gridX = (int)MathF.Round( ( localX - snappedPos.x ) / tileSizeInches );\r\n\t\t\tint gridY = (int)MathF.Round( ( localY - snappedPos.y ) / tileSizeInches );\r\n\r\n\t\t\tint ringLevel = Math.Max( GetRingForCoord( gridX ), GetRingForCoord( gridY ) );\r\n\t\t\tif ( ringLevel > MaxRings )\r\n\t\t\t\tringLevel = MaxRings;\r\n\r\n\t\t\tif ( ringLevel > outermostRenderedRing )\r\n\t\t\t\toutermostRenderedRing = ringLevel;\r\n\r\n\t\t\ttileData[(tileX, tileY)] = (ringLevel, gridX, gridY);\r\n\t\t}\r\n\r\n\t\t// Second pass: update body groups\r\n\t\tforeach ( var kvp in _tiles )\r\n\t\t{\r\n\t\t\tvar renderer = kvp.Value.renderer;\r\n\t\t\tif ( !renderer.IsValid() )\r\n\t\t\t\tcontinue;\r\n\r\n\t\t\tvar (ringLevel, gridX, gridY) = tileData[kvp.Key];\r\n\r\n\t\t\tTileType tileType = TileType.End;\r\n\t\t\tfloat rotation = 0f;\r\n\r\n\t\t\tif ( ringLevel == 0 )\r\n\t\t\t{\r\n\t\t\t\tif ( gridX == 0 && gridY == 0 ) rotation = 0f;\r\n\t\t\t\telse if ( gridX == 1 && gridY == 0 ) rotation = 90f;\r\n\t\t\t\telse if ( gridX == 0 && gridY == 1 ) rotation = 270f;\r\n\t\t\t\telse if ( gridX == 1 && gridY == 1 ) rotation = 180f;\r\n\t\t\t}\r\n\t\t\telse if ( ringLevel < MaxRings )\r\n\t\t\t{\r\n\t\t\t\t// Rings are 1-tile-thick outlines - every tile is an End or a Corner.\r\n\t\t\t\tif ( IsCornerTile( gridX, gridY, ringLevel ) )\r\n\t\t\t\t{\r\n\t\t\t\t\ttileType = TileType.Corner;\r\n\t\t\t\t\trotation = GetCornerRotation( gridX, gridY, ringLevel );\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\trotation = GetEdgeRotation( gridX, gridY, ringLevel );\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\trenderer.SetBodyGroup( \"subdivision\", GetBodyGroupForRing( ringLevel, tileType ) );\r\n\t\t\tkvp.Value.go.WorldRotation = Rotation.FromYaw( -90f + rotation );\r\n\t\t}\r\n\t}\r\n\r\n\tprivate int GetRingForCoord( int gridCoord )\r\n\t{\r\n\t\t// Center (ring 0): coords 0, 1 (the 2x2 quadrant block)\r\n\t\t// Ring N (1 tile thick outline): coord -N on the negative side, N+1 on the positive side\r\n\t\t//   Ring 1: -1, 2\r\n\t\t//   Ring 2: -2, 3\r\n\t\t//   Ring 3: -3, 4\r\n\t\tif ( gridCoord >= 0 && gridCoord <= 1 )\r\n\t\t\treturn 0;\r\n\r\n\t\tint ring = gridCoord < 0 ? -gridCoord : gridCoord - 1;\r\n\r\n\t\tif ( ring > MaxRings )\r\n\t\t\treturn MaxRings + 1;\r\n\r\n\t\treturn ring;\r\n\t}\r\n\r\n\tprivate bool IsCornerTile( int gridX, int gridY, int ringLevel )\r\n\t{\r\n\t\tint outerMin = -ringLevel;\r\n\t\tint outerMax = ringLevel + 1;\r\n\r\n\t\tbool atCornerX = ( gridX == outerMin || gridX == outerMax );\r\n\t\tbool atCornerY = ( gridY == outerMin || gridY == outerMax );\r\n\r\n\t\treturn atCornerX && atCornerY;\r\n\t}\r\n\r\n\tprivate float GetCornerRotation( int gridX, int gridY, int ringLevel )\r\n\t{\r\n\t\tint outerMin = -ringLevel;\r\n\t\tint outerMax = ringLevel + 1;\r\n\r\n\t\tif ( gridX == outerMin && gridY == outerMax ) return 270f;\r\n\t\tif ( gridX == outerMax && gridY == outerMax ) return 180f;\r\n\t\tif ( gridX == outerMax && gridY == outerMin ) return 90f;\r\n\t\tif ( gridX == outerMin && gridY == outerMin ) return 0f;\r\n\r\n\t\treturn 0f;\r\n\t}\r\n\r\n\tprivate float GetEdgeRotation( int gridX, int gridY, int ringLevel )\r\n\t{\r\n\t\tint outerMin = -ringLevel;\r\n\t\tint outerMax = ringLevel + 1;\r\n\r\n\t\tif ( gridY == outerMax ) return 180f;\r\n\t\tif ( gridX == outerMax ) return 90f;\r\n\t\tif ( gridY == outerMin ) return 0f;\r\n\t\tif ( gridX == outerMin ) return 270f;\r\n\r\n\t\treturn 0f;\r\n\t}\r\n\r\n\tprivate void SpawnTile( Vector3 worldPosition, int tileX, int tileY )\r\n\t{\r\n\t\tvar tileGo = new GameObject( true, $\"tile_{tileX}_{tileY}\" );\r\n\t\ttileGo.Parent = GameObject;\r\n\t\ttileGo.Tags.Add( \"reflection\" ); // excluded from the reflection camera so water doesn't reflect itself\r\n\t\ttileGo.WorldPosition = worldPosition;\r\n\t\ttileGo.LocalScale = Vector3.One * ( WaterTileSize / 100f );\r\n\r\n\t\tvar renderer = tileGo.Components.Create<ModelRenderer>();\r\n\t\trenderer.Model = WaterTileModel;\r\n\r\n\t\trenderer.SceneObject.Attributes.Set( \"BoundsMin\", _boundsMin );\r\n\t\trenderer.SceneObject.Attributes.Set( \"BoundsMax\", _boundsMax );\r\n\t\trenderer.Attributes.SetCombo( \"D_TRANSLUCENT\", 1 );\r\n\t\trenderer.Attributes.SetCombo( \"D_Enable_Water_Line\", 0 );\r\n\r\n\t\trenderer.SetBodyGroup( \"subdivision\", FarBodyGroupIndex );\r\n\r\n\t\t_tiles[(tileX, tileY)] = (tileGo, renderer);\r\n\r\n\t\t// New renderer needs the surface attributes, ripple attributes and the\r\n\t\t// reflection-mode combo even if nothing changed.\r\n\t\t_surfaceAttributesDirty = true;\r\n\t\tMarkRippleRenderersDirty();\r\n\t\t_lastWaterReflectionPlanarRendering = null;\r\n\t}\r\n}\r\n"
        },
        {
            "Ident": "ali3nsystems.lib-atmokinetic_assets",
            "Path": "Water/Modes/WorkInProgress/Area/WaterController.WaterArea.cs",
            "FileName": "WaterController.WaterArea.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 338612,
            "IsPrivate": false,
            "Code": "namespace AtmokineticAssets;\r\n\r\n// Soon TM\r\n// This will be used for very large body of waters like oceans, lakes, etc...\r\npublic sealed partial class WaterController: Component\r\n{\r\n\t// [Property, Title(\"Area Model\"), Feature(\"General\"), Group(\"Properties\"), ShowIf( nameof(WaterMode), WaterModeEnumeration.Area )]\r\n\t// public Model WaterAreaModel { get; set; }\r\n\r\n\t// [Property, Title(\"Bounds Minimum (cm)\"), Feature(\"General\"), Group(\"Properties\"), ShowIf( nameof(WaterMode), WaterModeEnumeration.Area )]\r\n\t// public Vector3 WaterAreaBoundsMinimum = new( -100, -100, -100 );\r\n\r\n\t// [Property, Title(\"Bounds Maximum (cm)\"), Feature(\"General\"), Group(\"Properties\"), ShowIf( nameof(WaterMode), WaterModeEnumeration.Area )]\r\n\t// public Vector3 WaterAreaBoundsMaximum = new( 100, 100, 0 );\r\n\r\n\t// [Button(\"Generate Water Area\"), Feature(\"General\"), Group(\"Properties\"), ShowIf( nameof(WaterMode), WaterModeEnumeration.Area )]\r\n\t// public void GenerateWaterArea()\r\n\t// {\r\n\r\n\t// }\r\n}\r\n"
        },
        {
            "Ident": "ali3nsystems.lib-atmokinetic_assets",
            "Path": "Water/Reflection/Wip/WaterController.Reflection.Cubemap.cs",
            "FileName": "WaterController.Reflection.Cubemap.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 338612,
            "IsPrivate": false,
            "Code": "// namespace AtmokineticAssets;\r\n\r\n// public sealed partial class WaterController : Component, Component.ExecuteInEditor\r\n// {\r\n// \tpublic bool ShowWaterReflectionCubemapSettings =>\r\n// \tWaterReflectionMode == WaterReflectionModeEnumeration.Dynamic\r\n// \t|| ( WaterReflectionMode == WaterReflectionModeEnumeration.Static\r\n// \t&& WaterReflectionModeStaticMode == WaterReflectionModeStaticModeEnumeration.Cubemap );\r\n// \tpublic bool ShowWaterReflectionCubemapResolutionDynamic => ShowWaterReflectionCubemapSettings && WaterReflectionCubemapResolutionMode == WaterReflectionCubemapResolutionModeEnumeration.Dynamic;\r\n// \tpublic bool ShowWaterReflectionCubemapResolutionStatic => ShowWaterReflectionCubemapSettings && WaterReflectionCubemapResolutionMode == WaterReflectionCubemapResolutionModeEnumeration.Static;\r\n// \tpublic bool ShowWaterReflectionCubemapFramesPerSecondDynamic => ShowWaterReflectionCubemapSettings && WaterReflectionCubemapFramesPerSecondMode == WaterReflectionCubemapFramesPerSecondModeEnumeration.Dynamic;\r\n// \tpublic bool ShowWaterReflectionCubemapFramesPerSecondStatic => ShowWaterReflectionCubemapSettings && WaterReflectionCubemapFramesPerSecondMode == WaterReflectionCubemapFramesPerSecondModeEnumeration.Static;\r\n\r\n// \tpublic enum WaterReflectionCubemapResolutionModeEnumeration\r\n// \t{\r\n// \t\tDynamic,\r\n// \t\tStatic\r\n// \t}\r\n\r\n// \t[Property, Feature(\"Reflection\"), Group(\"Cubemap\"), Header(\"Resolution\"), ShowIf( nameof(ShowWaterReflectionCubemapSettings), true )]\r\n// \tpublic WaterReflectionCubemapResolutionModeEnumeration WaterReflectionCubemapResolutionMode {get;set;} = WaterReflectionCubemapResolutionModeEnumeration.Dynamic;\r\n\r\n// \t[Property, Feature(\"Reflection\"), Group(\"Cubemap\"), ShowIf( nameof(ShowWaterReflectionCubemapResolutionDynamic), true )]\r\n// \tpublic int WaterReflectionCubemapResolutionDynamicUltra {get;set;} = 1;\r\n\r\n// \t[Property, Feature(\"Reflection\"), Group(\"Cubemap\"), ShowIf( nameof(ShowWaterReflectionCubemapResolutionDynamic), true )]\r\n// \tpublic int WaterReflectionCubemapResolutionDynamicHigh {get;set;} = 2;\r\n\r\n// \t[Property, Feature(\"Reflection\"), Group(\"Cubemap\"), ShowIf( nameof(ShowWaterReflectionCubemapResolutionDynamic), true )]\r\n// \tpublic int WaterReflectionCubemapResolutionDynamicMedium {get;set;} = 4;\r\n\r\n// \t[Property, Feature(\"Reflection\"), Group(\"Cubemap\"), ShowIf( nameof(ShowWaterReflectionCubemapResolutionDynamic), true )]\r\n// \tpublic int WaterReflectionCubemapResolutionDynamicLow {get;set;} = 8;\r\n\r\n// \t[Property, Feature(\"Reflection\"), Group(\"Cubemap\"), ShowIf( nameof(ShowWaterReflectionCubemapResolutionStatic), true )]\r\n// \tpublic int WaterReflectionCubemapResolutionStatic {get;set;} = 2;\r\n\r\n// \t/// <summary>\r\n// \t///\r\n// \t/// </summary>\r\n// \tpublic enum WaterReflectionCubemapFramesPerSecondModeEnumeration\r\n// \t{\r\n// \t\tDynamic,\r\n// \t\tStatic\r\n// \t}\r\n\r\n// \t[Property, Feature(\"Reflection\"), Group(\"Cubemap\"), Header(\"Frames Per Second\"), ShowIf( nameof(ShowWaterReflectionCubemapSettings), true )]\r\n// \tpublic WaterReflectionCubemapFramesPerSecondModeEnumeration WaterReflectionCubemapFramesPerSecondMode {get;set;} = WaterReflectionCubemapFramesPerSecondModeEnumeration.Dynamic;\r\n\r\n// \t[Property, Feature(\"Reflection\"), Group(\"Cubemap\"), ShowIf( nameof(ShowWaterReflectionCubemapFramesPerSecondDynamic), true )]\r\n// \tpublic float WaterReflectionCubemapFramesPerSecondDynamicUltra {get;set;} = 120f;\r\n\r\n// \t[Property, Feature(\"Reflection\"), Group(\"Cubemap\"), ShowIf( nameof(ShowWaterReflectionCubemapFramesPerSecondDynamic), true )]\r\n// \tpublic float WaterReflectionCubemapFramesPerSecondDynamicHigh {get;set;} = 60f;\r\n\r\n// \t[Property, Feature(\"Reflection\"), Group(\"Cubemap\"), ShowIf( nameof(ShowWaterReflectionCubemapFramesPerSecondDynamic), true )]\r\n// \tpublic float WaterReflectionCubemapFramesPerSecondDynamicMedium {get;set;} = 30f;\r\n\r\n// \t[Property, Feature(\"Reflection\"), Group(\"Cubemap\"), ShowIf( nameof(ShowWaterReflectionCubemapFramesPerSecondDynamic), true )]\r\n// \tpublic float WaterReflectionCubemapFramesPerSecondDynamicLow {get;set;} = 15f;\r\n\r\n// \t[Property, Feature(\"Reflection\"), Group(\"Cubemap\"), ShowIf( nameof(ShowWaterReflectionCubemapFramesPerSecondStatic), true )]\r\n// \tpublic float WaterReflectionCubemapFramesPerSecondStatic {get;set;} = 60f;\r\n// }\r\n"
        },
        {
            "Ident": "ali3nsystems.lib-atmokinetic_assets",
            "Path": "Code/Water/Modes/Tiles/WaterController.WaterTiles.Gizmo.cs",
            "FileName": "WaterController.WaterTiles.Gizmo.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 338612,
            "IsPrivate": false,
            "Code": "namespace AtmokineticAssets;\r\n\r\npublic sealed partial class WaterController : Component\r\n{\r\n\tprivate void DrawWaterTileGizmo()\r\n\t{\r\n\t\tif ( !EnableWaterTileGizmo )\r\n\t\t\treturn;\r\n\r\n\t\tif ( !Gizmo.IsSelected && !Gizmo.IsHovered )\r\n\t\t\treturn;\r\n\r\n\t\t// Raw box from the user's typed bounds \u2014 used for the editing handles.\r\n\t\tvar rawBox = new BBox(\r\n\t\t\tUnitConversion.CentimetersToInches( WaterTileBoundsMinimum ),\r\n\t\t\tUnitConversion.CentimetersToInches( WaterTileBoundsMaximum ) );\r\n\r\n\t\t// Snapped box \u2014 the actual world-grid region the tiles fill \u2014 used for drawing.\r\n\t\tvar snappedBox = GetSnappedLocalBox( rawBox );\r\n\r\n\t\tDrawWaterTileBounds( snappedBox );\r\n\t\tDrawWaterTileHandles( rawBox );\r\n\t}\r\n\r\n\t// Snaps the bounds extent to the tile grid relative to the GameObject's own origin,\r\n\t// so the box keeps its tile-sized footprint but follows the GameObject smoothly (no world-grid jumping).\r\n\tprivate BBox GetSnappedLocalBox( BBox localBox )\r\n\t{\r\n\t\tfloat tileSizeInches = UnitConversion.CentimetersToInches( WaterTileSize );\r\n\t\tif ( tileSizeInches <= 0 )\r\n\t\t\treturn localBox;\r\n\r\n\t\tfloat minX = MathF.Floor( MathF.Round( localBox.Mins.x / tileSizeInches, 4 ) ) * tileSizeInches;\r\n\t\tfloat minY = MathF.Floor( MathF.Round( localBox.Mins.y / tileSizeInches, 4 ) ) * tileSizeInches;\r\n\t\tfloat maxX = MathF.Ceiling( MathF.Round( localBox.Maxs.x / tileSizeInches, 4 ) ) * tileSizeInches;\r\n\t\tfloat maxY = MathF.Ceiling( MathF.Round( localBox.Maxs.y / tileSizeInches, 4 ) ) * tileSizeInches;\r\n\r\n\t\treturn new BBox(\r\n\t\t\tnew Vector3( minX, minY, localBox.Mins.z ),\r\n\t\t\tnew Vector3( maxX, maxY, localBox.Maxs.z ) );\r\n\t}\r\n\r\n\tprivate void DrawWaterTileBounds( BBox box )\r\n\t{\r\n\t\tGizmo.Draw.LineThickness = 1;\r\n\t\tvar tint = Gizmo.IsSelected ? Gizmo.Colors.Blue : Gizmo.Colors.Blue.WithAlpha( 0.4f );\r\n\r\n\t\tfor ( int i = 0; i < 2; i++ )\r\n\t\t{\r\n\t\t\tbool xray = i == 0;\r\n\t\t\tGizmo.Draw.IgnoreDepth = xray;\r\n\t\t\tfloat alpha = xray ? 0.2f : 1f;\r\n\r\n\t\t\tGizmo.Draw.Color = tint.WithAlpha( tint.a * alpha );\r\n\t\t\tGizmo.Draw.LineBBox( box );\r\n\r\n\t\t\tGizmo.Draw.Color = tint.WithAlpha( 0.05f * alpha );\r\n\t\t\tGizmo.Draw.SolidBox( box );\r\n\t\t}\r\n\t}\r\n\t\r\n\tprivate void DrawWaterTileHandles( BBox box )\r\n\t{\r\n\t\tGizmo.Hitbox.BBox( box );\r\n\r\n\t\t// Disable the editor's grid snap for this control so it doesn't double up with our tile snap.\r\n\t\tvar previousSnap = Gizmo.Settings.SnapToGrid;\r\n\t\tGizmo.Settings.SnapToGrid = false;\r\n\r\n\t\tif ( Gizmo.Control.BoundingBox( \"water_tile_bounds\", box, out var newBox ) )\r\n\t\t{\r\n\t\t\tvar minCm = UnitConversion.InchesToCentimeters( newBox.Mins );\r\n\t\t\tvar maxCm = UnitConversion.InchesToCentimeters( newBox.Maxs );\r\n\r\n\t\t\tWaterTileBoundsMinimum = SnapChangedAxes( WaterTileBoundsMinimum, minCm, WaterTileSize, floor: true );\r\n\t\t\tWaterTileBoundsMaximum = SnapChangedAxes( WaterTileBoundsMaximum, maxCm, WaterTileSize, floor: false );\r\n\t\t}\r\n\r\n\t\tGizmo.Settings.SnapToGrid = previousSnap;\r\n\t}\r\n\r\n\tprivate float ResizeWaterTileBounds( float oldSize, float newSize )\r\n\t{\r\n\t\t// Skip during deserialization / before start \u2014 only rescale on genuine edits.\r\n\t\tif ( !_tilesStarted || oldSize == newSize || newSize <= 0 || oldSize <= 0 )\r\n\t\t\treturn newSize;\r\n\r\n\t\tfloat scale = newSize / oldSize;\r\n\r\n\t\t// Scale X/Y of the bounds so the box stays the same tile count.\r\n\t\tWaterTileBoundsMinimum = new Vector3( WaterTileBoundsMinimum.x * scale, WaterTileBoundsMinimum.y * scale, WaterTileBoundsMinimum.z );\r\n\t\tWaterTileBoundsMaximum = new Vector3( WaterTileBoundsMaximum.x * scale, WaterTileBoundsMaximum.y * scale, WaterTileBoundsMaximum.z );\r\n\r\n\t\treturn newSize;\r\n\t}\r\n\r\n\tprivate static Vector3 SnapChangedAxes( Vector3 oldValue, Vector3 newValue, float tileSize, bool floor )\r\n\t{\r\n\t\tif ( tileSize <= 0 )\r\n\t\t\treturn newValue;\r\n\r\n\t\tfloat SnapAxis( float n ) =>\r\n\t\t\t( floor ? MathF.Floor( n / tileSize ) : MathF.Ceiling( n / tileSize ) ) * tileSize;\r\n\r\n\t\treturn new Vector3(\r\n\t\t\toldValue.x.AlmostEqual( newValue.x ) ? oldValue.x : SnapAxis( newValue.x ),\r\n\t\t\toldValue.y.AlmostEqual( newValue.y ) ? oldValue.y : SnapAxis( newValue.y ),\r\n\t\t\toldValue.z.AlmostEqual( newValue.z ) ? oldValue.z : newValue.z );\r\n\t}\r\n}\r\n"
        },
        {
            "Ident": "ali3nsystems.lib-atmokinetic_assets",
            "Path": "Code/Water/WaterController.Buoyancy.cs",
            "FileName": "WaterController.Buoyancy.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 338612,
            "IsPrivate": false,
            "Code": "namespace AtmokineticAssets;\r\n\r\npublic sealed partial class WaterController : Component, Component.ExecuteInEditor\r\n{\r\n    \r\n   \t/// <summary>\r\n\t/// How heavy the fluid is. Higher values make things float more.\r\n\t/// Water is 1000, oil is around 900, something very heavy like mercury is 13600.\r\n\t/// </summary>\r\n\t[Property, Feature(\"Buoyancy\"), Group( \"Fluid\" ), Title(\"Fluid Density\"), Range( 0, 15000 )]\r\n\t[Sync]\r\n\tpublic float WaterBuoyancyFluidDensity { get; set; } = 1000.0f;\r\n\r\n\t/// <summary>\r\n\t/// Direction and speed of the water current.\r\n\t/// </summary>\r\n\t[Property, Feature(\"Buoyancy\"), Group( \"Fluid\" ), Title(\"Fluid Velocity\")]\r\n\t[Sync]\r\n\tpublic Vector3 WaterBuoyancyFluidVelocity { get; set; } = Vector3.Zero;\r\n\r\n\t/// <summary>\r\n\t/// How much the fluid slows down movement.\r\n\t/// </summary>\r\n\t[Property, Feature(\"Buoyancy\"), Group( \"Fluid\" ), Title(\"Linear Drag\"),Range( 0, 1 )]\r\n\t[Sync]\r\n\tpublic float WaterBuoyancyLinearDrag { get; set; } = 0.1f;\r\n\r\n\t/// <summary>\r\n\t/// How much the fluid slows down spinning.\r\n\t/// </summary>\r\n\t[Property, Feature(\"Buoyancy\"), Group( \"Fluid\" ), Title(\"Angular Drag\"), Range( 0, 2 )]\r\n\t[Sync]\r\n\tpublic float WaterBuoyancyAngularDrag { get; set; } = 0.5f;\r\n\r\n\t/// <summary>\r\n\t/// Moves the water surface up or down.\r\n\t/// </summary>\r\n\t[Property, Feature(\"Buoyancy\"), Group( \"Surface\" ), Title(\"Surface Offset\")]\r\n\t[Sync]\r\n\tpublic float WaterBuoyancySurfaceOffset { get; set; } = 0.0f;\r\n\r\n\t/// <summary>\r\n\t/// How tall the waves are. Set to 0 for calm water.\r\n\t/// </summary>\r\n\t[Property, Feature(\"Buoyancy\"), Group( \"Surface\" ), Title(\"Wave Amplitude\"), Range( 0, 100 )]\r\n\t[Sync]\r\n\tpublic float WaterBuoyancyWaveAmplitude { get; set; } = 0.1f;\r\n\r\n\t/// <summary>\r\n\t/// How fast the waves move.\r\n\t/// </summary>\r\n\t[Property, Feature(\"Buoyancy\"), Group( \"Surface\" ), Title(\"Wave Frequency\"), Range( 0, 5 )]\r\n\t[Sync]\r\n\tpublic float WaterBuoyancyWaveFrequency { get; set; } = 0.5f;\r\n\r\n\t/// <summary>\r\n\t/// Objects with any of these tags won't be affected.\r\n\t/// </summary>\r\n\t[Property, Feature(\"Buoyancy\"), Group( \"Filtering\" ), Title(\"Ignore Tags\")]\r\n\t[Sync]\r\n\tpublic TagSet WaterBuoyancyIgnoreTags { get; set; } = [];\r\n\r\n\tprivate WaterVolume _waterBuoyancy;\r\n\r\n\tpublic void CreateWaterBuoyancy()\r\n\t{\r\n\t\t_waterBuoyancy ??= Components.Get<Sandbox.WaterVolume>( FindMode.InSelf ) ?? Components.Create<Sandbox.WaterVolume>();\r\n\t\t_waterBuoyancy.Flags |= ComponentFlags.Hidden;\r\n\r\n\t\t_waterBuoyancy.FluidDensity = WaterBuoyancyFluidDensity;\r\n\t\t_waterBuoyancy.FluidVelocity = WaterBuoyancyFluidVelocity;\r\n\t\t_waterBuoyancy.LinearDrag = WaterBuoyancyLinearDrag;\r\n\t\t_waterBuoyancy.AngularDrag = WaterBuoyancyAngularDrag;\r\n\t\t_waterBuoyancy.SurfaceOffset = _waterZ - WorldPosition.z + WaterBuoyancySurfaceOffset;\r\n\t\t_waterBuoyancy.WaveAmplitude = WaterBuoyancyWaveAmplitude;\r\n\t\t_waterBuoyancy.WaveFrequency = WaterBuoyancyWaveFrequency;\r\n\t\t_waterBuoyancy.IgnoreTags = WaterBuoyancyIgnoreTags;\r\n\t}\r\n}"
        },
        {
            "Ident": "ali3nsystems.lib-atmokinetic_assets",
            "Path": "Graphics/GraphicsSettings.TerrainSettings.cs",
            "FileName": "GraphicsSettings.TerrainSettings.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 338612,
            "IsPrivate": false,
            "Code": "namespace AtmokineticAssets;\r\n\r\npublic sealed partial class GraphicsSettings : Component, Component.ExecuteInEditor\r\n{\r\n    //Terrain Settings\r\n    public enum TerrainRenderingModeEnumeration\r\n    {\r\n        [Title(\"Parallax Occlusion Mapping\")]\r\n        ParallaxOcclusionMapping,\r\n        \r\n        [Title(\"Bump Offset\")]\r\n        BumpOffset,\r\n       \r\n        Standard\r\n    }\r\n\r\n    //[Property, Feature(\"Terrain\"), Title(\"Rendering\")]\r\n    public TerrainRenderingModeEnumeration TerrainRenderingMode { get; set; } = TerrainRenderingModeEnumeration.Standard;\r\n\r\n    public enum TerrainRenderingParallaxOcclusionMappingQualityEnumeration\r\n    {\r\n        Ultra,\r\n        High,\r\n        Medium,\r\n        Low\r\n    }\r\n\r\n    //[Property, Feature(\"Terrain\"), Title(\"Quality\")] \r\n    [ShowIf(nameof(TerrainRenderingMode), TerrainRenderingModeEnumeration.ParallaxOcclusionMapping)]\r\n    public TerrainRenderingParallaxOcclusionMappingQualityEnumeration TerrainRenderingParallaxOcclusionMapping { get; set; } = TerrainRenderingParallaxOcclusionMappingQualityEnumeration.Medium;\r\n\r\n    public enum TerrainRenderingBumpOffsetQualityEnumeration\r\n    {\r\n        Ultra,\r\n        High,\r\n        Medium,\r\n        Low\r\n    }\r\n   \r\n    //[Property, Feature(\"Terrain\"), Title(\"Quality\")] \r\n    [ShowIf(nameof(TerrainRenderingMode), TerrainRenderingModeEnumeration.BumpOffset)]\r\n    public TerrainRenderingParallaxOcclusionMappingQualityEnumeration TerrainRenderingBumpOffset { get; set; } = TerrainRenderingParallaxOcclusionMappingQualityEnumeration.Medium;\r\n\r\n\r\n    // public void ChangeTerrainSettings()\r\n    // {\r\n    //             // Terrain rendering mode: 0 = POM, 1 = Bump Offset, 2 = Standard\r\n    //     int terrainRenderingMode = TerrainRenderingMode switch\r\n    //     {\r\n    //         TerrainRenderingModeEnumeration.ParallaxOcclusionMapping => 0,\r\n    //         TerrainRenderingModeEnumeration.BumpOffset => 1,\r\n    //         TerrainRenderingModeEnumeration.Standard => 2,\r\n    //         _ => 1\r\n    //     };\r\n\r\n    //     // Quality: Ultra = 1, High = 2, Medium = 4, Low = 8\r\n    //     float quality;\r\n    //     switch ( TerrainRenderingMode )\r\n    //     {\r\n    //         case TerrainRenderingModeEnumeration.ParallaxOcclusionMapping:\r\n    //             quality = TerrainRenderingParallaxOcclusionMapping switch\r\n    //             {\r\n    //                 TerrainRenderingParallaxOcclusionMappingQualityEnumeration.Ultra => 1f,\r\n    //                 TerrainRenderingParallaxOcclusionMappingQualityEnumeration.High => 2f,\r\n    //                 TerrainRenderingParallaxOcclusionMappingQualityEnumeration.Medium => 4f,\r\n    //                 TerrainRenderingParallaxOcclusionMappingQualityEnumeration.Low => 8f,\r\n    //                 _ => 4f\r\n    //             };\r\n    //             break;\r\n\r\n    //         case TerrainRenderingModeEnumeration.BumpOffset:\r\n    //             quality = TerrainRenderingBumpOffset switch\r\n    //             {\r\n    //                 TerrainRenderingParallaxOcclusionMappingQualityEnumeration.Ultra => 1f,\r\n    //                 TerrainRenderingParallaxOcclusionMappingQualityEnumeration.High => 2f,\r\n    //                 TerrainRenderingParallaxOcclusionMappingQualityEnumeration.Medium => 4f,\r\n    //                 TerrainRenderingParallaxOcclusionMappingQualityEnumeration.Low => 8f,\r\n    //                 _ => 4f\r\n    //             };\r\n    //             break;\r\n\r\n    //         default:\r\n    //             quality = 4f;\r\n    //             break;\r\n    //     }\r\n\r\n    //     // Enumerate live \u2014 objects spawned after startup (e.g. baked water tiles) must be included.\r\n    //     foreach ( var sceneObject in Scene.SceneWorld.SceneObjects )\r\n    //     {\r\n    //         if ( sceneObject is null || !sceneObject.IsValid() ) continue;\r\n    //         sceneObject.Attributes.Set( \"D_Rendering\", terrainRenderingMode );\r\n    //         sceneObject.Attributes.Set( \"Quality\", quality );\r\n    //     }\r\n    // }\r\n}"
        },
        {
            "Ident": "ali3nsystems.lib-atmokinetic_assets",
            "Path": "Utility/UnitConversion.cs",
            "FileName": "UnitConversion.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 338612,
            "IsPrivate": false,
            "Code": "namespace AtmokineticAssets;\r\n\r\npublic static class UnitConversion\r\n{\r\n\t//Inches To Meters\r\n\tpublic static float InchesToMeters( float inches )\r\n\t{\r\n\t\treturn inches * 0.0254f;\r\n\t}\r\n\r\n\tpublic static Vector2 InchesToMeters( Vector2 inches )\r\n\t{\r\n\t\treturn inches * 0.0254f;\r\n\t}\r\n\r\n\tpublic static Vector3 InchesToMeters( Vector3 inches )\r\n\t{\r\n\t\treturn inches * 0.0254f;\r\n\t}\r\n\r\n\tpublic static Vector4 InchesToMeters( Vector4 inches )\r\n\t{\r\n\t\treturn inches * 0.0254f;\r\n\t}\r\n\r\n\r\n\r\n\t//Inches To Centimeters\r\n\tpublic static float InchesToCentimeters( float inches)\r\n\t{\r\n\t\treturn inches * 2.54f;\r\n\t}\r\n\r\n\tpublic static Vector2 InchesToCentimeters( Vector2 inches)\r\n\t{\r\n\t\treturn inches * 2.54f;\r\n\t}\r\n\r\n\tpublic static Vector3 InchesToCentimeters( Vector3 inches)\r\n\t{\r\n\t\treturn inches * 2.54f;\r\n\t}\r\n\r\n\tpublic static Vector4 InchesToCentimeters( Vector4 inches)\r\n\t{\r\n\t\treturn inches * 2.54f;\r\n\t}\r\n\r\n\t//Centimeters To Inches\r\n\tpublic static float CentimetersToInches( float centimeters )\r\n\t{\r\n\t\treturn centimeters / 2.54f;\r\n\t}\r\n\t\r\n\tpublic static Vector2 CentimetersToInches( Vector2 centimeters )\r\n\t{\r\n\t\treturn centimeters / 2.54f;\r\n\t}\r\n\t\r\n\tpublic static Vector3 CentimetersToInches( Vector3 centimeters )\r\n\t{\r\n\t\treturn centimeters / 2.54f;\r\n\t}\r\n\t\r\n\tpublic static Vector4 CentimetersToInches( Vector4 centimeters )\r\n\t{\r\n\t\treturn centimeters / 2.54f;\r\n\t}\r\n\r\n\t//Meters To Centimeters\r\n\tpublic static float MetersToCentimeters( float meters )\r\n\t{\r\n\t\treturn meters * 100f;\r\n\t}\r\n\r\n\t//Meters To Inches\r\n\tpublic static float MetersToInches(float meters)\r\n\t{\r\n\t\treturn meters / 0.0254f;\r\n\t}\r\n}\r\n"
        },
        {
            "Ident": "ali3nsystems.lib-atmokinetic_assets",
            "Path": "Water/Modes/WorkInProgress/Area/WaterController.WaterArea.Gizmo.cs",
            "FileName": "WaterController.WaterArea.Gizmo.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 338612,
            "IsPrivate": false,
            "Code": "namespace AtmokineticAssets;\r\n\r\npublic sealed partial class WaterController: Component\r\n{\r\n\t// [Property, Title(\"Enable Water Gizmo\"),Feature( \"General\" ), ShowIf( nameof(WaterMode), WaterModeEnumeration.Area)]\r\n\t// public bool EnableWaterAreaGizmo { get; set; } = true;\r\n}\r\n"
        },
        {
            "Ident": "ali3nsystems.lib-atmokinetic_assets",
            "Path": "Water/WaterController.Surface.cs",
            "FileName": "WaterController.Surface.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 338612,
            "IsPrivate": false,
            "Code": "namespace AtmokineticAssets;\r\n\r\n// Also add:\r\n\r\n//Ice\r\n//Caustics\r\n\r\npublic sealed partial class WaterController : Component, Component.ExecuteInEditor\r\n{\r\n\t// Surface\r\n\t[Property, Feature(\"Attributes\"), Group(\"Surface\"), Title(\"Color\"), Order(1)]\r\n\t[Sync]\r\n\tpublic Color WaterSurfaceColor { get; set { field = value; _surfaceAttributesDirty = true; } } = Color.Parse( \"#c5d9ff\" ).Value;\r\n\r\n\t[Property, Feature(\"Attributes\"), Group(\"Surface\"), Title(\"Blur\"), Order(2)]\r\n\t[Sync]\r\n\tpublic float WaterSurfaceBlur { get; set { field = value; _surfaceAttributesDirty = true; } } = 0;\r\n\r\n\t// Reflection\r\n\t[Property, Feature(\"Attributes\"), Group(\"Reflection\"), Title(\"Color\"), Order(3)]\r\n\t[Sync]\r\n\tpublic Color WaterReflectionColor { get; set { field = value; _surfaceAttributesDirty = true; } } = Color.Parse( \"#c5d9ff\" ).Value;\r\n\r\n\t[Property, Feature(\"Attributes\"), Group(\"Reflection\"), Title(\"Blur\"), Order(4)]\r\n\t[Sync]\r\n\tpublic float WaterReflectionBlur { get; set { field = value; _surfaceAttributesDirty = true; } } = 0;\r\n\r\n\t[Property, Feature(\"Attributes\"), Group(\"Reflection\"), Title(\"Distance\"), Order(5)]\r\n\t[Sync]\r\n\tpublic float WaterReflectionDistance { get; set { field = value; _surfaceAttributesDirty = true; } } = 4f;\r\n\r\n\t[Property, Feature(\"Attributes\"), Group(\"Reflection\"), Title(\"Threshold\"), Order(6)]\r\n\t[Sync]\r\n\tpublic Vector2 WaterReflectionThreshold { get; set { field = value; _surfaceAttributesDirty = true; } } = new Vector2( 0, 0.25f );\r\n\r\n\t// Shoreline\r\n\t[Property, Feature(\"Attributes\"), Group(\"Shoreline\"), Title(\"Fade\"), Order(7)]\r\n\t[Sync]\r\n\tpublic float WaterShorelineFade { get; set { field = value; _surfaceAttributesDirty = true; } } = 2f;\r\n\r\n\t// Tension\r\n\t[Property, Feature(\"Attributes\"), Group(\"Tension\"), Title(\"Distance\"), Order(8)]\r\n\t[Sync]\r\n\tpublic float WaterTensionDistance { get; set { field = value; _surfaceAttributesDirty = true; } } = 2f;\r\n\r\n\t[Property, Feature(\"Attributes\"), Group(\"Tension\"), Title(\"Refraction\"), Order(9)]\r\n\t[Sync]\r\n\tpublic float WaterTensionRefraction { get; set { field = value; _surfaceAttributesDirty = true; } } = 0.1f;\r\n\r\n\t// Texture A\r\n\t[Property, Feature(\"Attributes\"), Group(\"Water Ripples A\"), Title(\"Texture A\"), Order(10)]\r\n\t[Sync]\r\n\tpublic Texture WaterRipplesTextureA { get; set { field = value; _surfaceAttributesDirty = true; } } = Texture.Load(\"textures/tex-water_ripples_1.vtex\");\r\n\r\n\t[Property, Feature(\"Attributes\"), Group(\"Water Ripples A\"), Title(\"Scale A\"), Order(11)]\r\n\t[Sync]\r\n\tpublic float WaterRipplesScaleA { get; set { field = value; _surfaceAttributesDirty = true; } } = 0.01f;\r\n\r\n\t[Property, Feature(\"Attributes\"), Group(\"Water Ripples A\"), Title(\"Rotation A\"), Order(12)]\r\n\t[Sync]\r\n\tpublic float WaterRipplesRotationA { get; set { field = value; _surfaceAttributesDirty = true; } } = 0f;\r\n\r\n\t[Property, Feature(\"Attributes\"), Group(\"Water Ripples A\"), Title(\"Speed A\"), Order(13)]\r\n\t[Sync]\r\n\tpublic float WaterRipplesSpeedA { get; set { field = value; _surfaceAttributesDirty = true; } } = 0.1f;\r\n\r\n\t[Property, Feature(\"Attributes\"), Group(\"Water Ripples A\"), Title(\"Refraction A\"), Order(14)]\r\n\t[Sync]\r\n\tpublic float WaterRipplesRefractionA { get; set { field = value; _surfaceAttributesDirty = true; } } = 0.05f;\r\n\r\n\t[Property, Feature(\"Attributes\"), Group(\"Water Ripples A\"), Title(\"Displacement A\"), Order(15)]\r\n\t[Sync]\r\n\tpublic float WaterRipplesDisplacementA { get; set { field = value; _surfaceAttributesDirty = true; } } = 0.5f;\r\n\r\n\t// Texture B\r\n\t[Property, Feature(\"Attributes\"), Group(\"Water Ripples B\"), Title(\"Texture B\"), Order(16)]\r\n\t[Sync]\r\n\tpublic Texture WaterRipplesTextureB { get; set { field = value; _surfaceAttributesDirty = true; } } = Texture.Load(\"textures/tex-water_ripples_2.vtex\");\r\n\r\n\t[Property, Feature(\"Attributes\"), Group(\"Water Ripples B\"), Title(\"Scale B\"), Order(17)]\r\n\t[Sync]\r\n\tpublic float WaterRipplesScaleB { get; set { field = value; _surfaceAttributesDirty = true; } } = 0.01f;\r\n\r\n\t[Property, Feature(\"Attributes\"), Group(\"Water Ripples B\"), Title(\"Rotation B\"), Order(18)]\r\n\t[Sync]\r\n\tpublic float WaterRipplesRotationB { get; set { field = value; _surfaceAttributesDirty = true; } } = 180f;\r\n\r\n\t[Property, Feature(\"Attributes\"), Group(\"Water Ripples B\"), Title(\"Speed B\"), Order(19)]\r\n\t[Sync]\r\n\tpublic float WaterRipplesSpeedB { get; set { field = value; _surfaceAttributesDirty = true; } } = 0.1f;\r\n\r\n\t[Property, Feature(\"Attributes\"), Group(\"Water Ripples B\"), Title(\"Refraction B\"), Order(20)]\r\n\t[Sync]\r\n\tpublic float WaterRipplesRefractionB { get; set { field = value; _surfaceAttributesDirty = true; } } = 0.05f;\r\n\r\n\t[Property, Feature(\"Attributes\"), Group(\"Water Ripples B\"), Title(\"Displacement B\"), Order(21)]\r\n\t[Sync]\r\n\tpublic float WaterRipplesDisplacementB { get; set { field = value; _surfaceAttributesDirty = true; } } = 0.5f;\r\n\r\n\t// Starts dirty so the first WaterSurfaceAttributes call always pushes.\r\n\tprivate bool _surfaceAttributesDirty = true;\r\n\r\n\t[Rpc.Broadcast]\r\n\tpublic void WaterSurfaceAttributes()\r\n\t{\r\n\t\tif ( !_surfaceAttributesDirty )\r\n\t\t\treturn;\r\n\r\n\t\t_surfaceAttributesDirty = false;\r\n\r\n\t\tforeach ( var renderer in GetTileRenderers() )\r\n\t\t{\r\n\r\n\t\t\tif (WaterSurfaceBlur != 0)\r\n\t\t\t{\t\r\n\t\t\t\trenderer.Attributes.SetCombo(\"D_Enable_Water_Surface_Blur\", 1);\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\trenderer.Attributes.SetCombo(\"D_Enable_Water_Surface_Blur\", 0);\r\n\t\t\t}\r\n\r\n\t\t\tif (WaterReflectionBlur != 0)\r\n\t\t\t{\r\n\t\t\t\trenderer.Attributes.SetCombo(\"D_Enable_Water_Reflection_Blur\", 1);\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\trenderer.Attributes.SetCombo(\"D_Enable_Water_Reflection_Blur\", 0);\r\n\t\t\t}\r\n\r\n\r\n\t\t\t// Surface\r\n\t\t\trenderer.Attributes.Set( \"Water Surface Color\", WaterSurfaceColor );\r\n\t\t\trenderer.Attributes.Set( \"Water Surface Blur\", WaterSurfaceBlur );\r\n\r\n\t\t\t// Reflection\r\n\t\t\trenderer.Attributes.Set( \"Water Reflection Color\", WaterReflectionColor );\r\n\t\t\trenderer.Attributes.Set( \"Water Reflection Blur\", WaterReflectionBlur );\r\n\t\t\trenderer.Attributes.Set( \"Water Reflection Distance\", WaterReflectionDistance );\r\n\t\t\trenderer.Attributes.Set( \"Water Reflection Threshold\", WaterReflectionThreshold );\r\n\r\n\t\t\t// Shoreline\r\n\t\t\trenderer.Attributes.Set( \"Water Shoreline Fade\", WaterShorelineFade );\r\n\t\t\t\r\n\t\t\t// Tension\r\n\t\t\trenderer.Attributes.Set( \"Water Tension Distance\", WaterTensionDistance );\r\n\t\t\trenderer.Attributes.Set( \"Water Tension Refraction\", WaterTensionRefraction );\r\n\r\n\t\t\t// Water Ripples A\r\n\t\t\trenderer.Attributes.Set( \"WaterRipplesTextureA\", WaterRipplesTextureA );\r\n\t\t\trenderer.Attributes.Set( \"Water Ripples Scale A\", WaterRipplesScaleA );\r\n\t\t\trenderer.Attributes.Set( \"Water Ripples Rotation A\", WaterRipplesRotationA );\r\n\t\t\trenderer.Attributes.Set( \"Water Ripples Speed A\", WaterRipplesSpeedA );\r\n\t\t\trenderer.Attributes.Set( \"Water Ripples Refraction A\", WaterRipplesRefractionA );\r\n\t\t\trenderer.Attributes.Set( \"Water Ripples Displacement A\", WaterRipplesDisplacementA );\r\n\r\n\t\t\t// Water Ripples B\r\n\t\t\trenderer.Attributes.Set( \"WaterRipplesTextureB\", WaterRipplesTextureB );\r\n\t\t\trenderer.Attributes.Set( \"Water Ripples Scale B\", WaterRipplesScaleB );\r\n\t\t\trenderer.Attributes.Set( \"Water Ripples Rotation B\", WaterRipplesRotationB );\r\n\t\t\trenderer.Attributes.Set( \"Water Ripples Speed B\", WaterRipplesSpeedB );\r\n\t\t\trenderer.Attributes.Set( \"Water Ripples Refraction B\", WaterRipplesRefractionB );\r\n\t\t\trenderer.Attributes.Set( \"Water Ripples Displacement B\", WaterRipplesDisplacementB );\r\n\t\t}\r\n\t}\r\n}"
        },
        {
            "Ident": "ali3nsystems.lib-atmokinetic_assets",
            "Path": "Environment/EnvironmentController.cs",
            "FileName": "EnvironmentController.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 338612,
            "IsPrivate": false,
            "Code": "namespace AtmokineticAssets;\r\n\r\n[Title(\"Atmokinetic Assets - Environment Controller\")]\r\n[Category(\"Atmokinetic Assets\")]\r\n[Icon(\"cloud\")]\r\n\r\npublic sealed partial class EnvironmentController : Component\r\n{\r\n\tprotected override void OnAwake()\r\n\t{\r\n\t\tCacheWaterControllers();\r\n\t}\r\n\r\n\tprotected override void OnUpdate()\r\n\t{\r\n\t\t\r\n\t} \r\n\r\n\t// public enum PrecipitationModeEnumeration\r\n\t// {\r\n\t// \tNone,\r\n\t// \tRain,\r\n\t// \tSnow\r\n\t// }\r\n\r\n\t// [Property, Feature(\"Weather\"), Sync]\r\n\t// public PrecipitationModeEnumeration PrecipitationMode { get; set; } = PrecipitationModeEnumeration.None;\r\n\r\n\t// [Property, Feature(\"Weather\")] PrefabScene PrecipitationController { get; set;}\r\n\r\n\t// [Property, Feature(\"Weather\"), Range( 0f, 1f ), Sync]\r\n\t// public float WaterPuddles { get; set; } = 0f;\r\n\r\n\t// [Property, Feature(\"Weather\"), Range( 0.0001f, 1f ), Sync]\r\n\t// public float WaterLevel { get; set; } = 0f;\r\n\r\n\t// [Property, Feature(\"Weather\"), Range( 0, 4 ), Step(1), Sync]\r\n\t// public float PrecipitationIntensity { get; set; } = 0f;\r\n\r\n\t// [Property,Feature(\"Time\"), Sync]\r\n\t// public GameObject Sun {get;set;}\r\n\r\n\t// [Property, Feature(\"Time\"), Range(0,24), Sync]\r\n\t// public float Hour {get;set;} = 6f;\r\n\t\r\n\t// [Property, Feature(\"Time\"), Range(1,31), Step(1)]\r\n\t// public float Day {get;set;} = 0f;\r\n\t\r\n\t// [Property, Feature(\"Time\"), Range(1,12), Step(1)]\r\n\t// public float Month {get;set;} = 0f;\r\n\t\r\n\t// [Property, Feature(\"Time\"), Step(1)]\r\n\t// public float Year {get;set;} = 0f;\r\n\r\n\t// private float _lastWaterPuddlesValue = -1f;\r\n\t// private float _lastPrecipitationIntensityValue = -1f;\r\n\t// private float _lastPrecipitationIntensityValueForParticles = -1f;\r\n\t// private float _lastWaterLevelValue = -1f;\r\n\t\r\n\t// private List<SceneObject> _cachedSceneObjects;\r\n\t// private List<EnvmapProbe> _cachedEnvmapProbes;\r\n\r\n\t// // Rain Precipitation\r\n\t// private GameObject _rainGameObject;\r\n\t// private ParticleEffect _particleEffectRain;\r\n\t// private PrecipitationController _precipitationControllerRain;\r\n\t// private PrecipitationControllerEmitter _precipitationControllerRainEmitter;\r\n\t// private bool _activePrecipitationControllerRain;\r\n\r\n\t// // Snow Precipitation\r\n\t// private GameObject _snowGameObject;\r\n\t// private ParticleEffect _particleEffectSnow;\r\n\t// private PrecipitationController _precipitationControllerSnow;\r\n\t// private PrecipitationControllerEmitter _precipitationControllerSnowEmitter;\r\n\t// private bool _activePrecipitationControllerSnow;\r\n\r\n\t// private PrecipitationModeEnumeration _lastPrecipitationMode = PrecipitationModeEnumeration.None;\r\n\r\n\t// // Lerp state\r\n\t// private float _startMaxParticles;\r\n\t// private float _startRate;\r\n\t// private float _targetMaxParticles;\r\n\t// private float _targetRate;\r\n\t// private float _transitionProgress = 1f;\r\n\t// private float _lastHour = -1f;\r\n\r\n\t// protected override void OnStart()\r\n\t// {\r\n\t// \t// Cache all scene objects on startup\r\n\t// \t_cachedSceneObjects = Scene.SceneWorld.SceneObjects.Where( x => x != null ).ToList();\r\n\r\n\t// \t// Cache all environment map probes\r\n\t// \t_cachedEnvmapProbes = Scene.GetAllComponents<EnvmapProbe>().ToList();\r\n\t// }\r\n\t\r\n\t// [Rpc.Broadcast]\r\n\t// protected override void OnUpdate()\r\n\t// {\r\n\t// \tHandlePrecipitationModeChange();\r\n\t// \tPrecipitationEffects();\r\n\t// \tTimeEffects();\r\n\t// }\r\n\r\n\t// protected override void DrawGizmos()\r\n\t// {\r\n\t// \tif (_cachedSceneObjects == null)\r\n\t// \t{\r\n\t// \t\t_cachedSceneObjects = Scene.SceneWorld.SceneObjects.Where( x => x != null ).ToList();\r\n\t// \t}\r\n\r\n\t// \tif (_cachedEnvmapProbes == null)\r\n\t// \t{\r\n\t// \t\t_cachedEnvmapProbes = Scene.GetAllComponents<EnvmapProbe>().ToList();\r\n\t// \t}\r\n\r\n\t// \tif ( WaterPuddles != _lastWaterPuddlesValue || WaterLevel != _lastWaterLevelValue || PrecipitationIntensity != _lastPrecipitationIntensityValue)\r\n\t// \t{\r\n\t// \t\t_lastWaterPuddlesValue = WaterPuddles;\r\n\t// \t\t_lastWaterLevelValue = WaterLevel;\r\n\t// \t\t_lastPrecipitationIntensityValue = PrecipitationIntensity;\r\n\r\n\t// \t\tforeach ( var sceneModel in _cachedSceneObjects )\r\n\t// \t\t{\r\n\t// \t\t\tsceneModel.Attributes.Set( \"Water Puddles Accumulation\", WaterPuddles );\r\n\t// \t\t\tsceneModel.Attributes.Set( \"Water Plane Level\", WaterLevel );\r\n\t// \t\t\tsceneModel.Attributes.Set( \"Rain Ripples Intensity\", PrecipitationIntensity );\r\n\t// \t\t}\r\n\t// \t}\r\n\r\n\t// \tTimeEffects();\r\n\t// }\r\n\r\n\t// private void HandlePrecipitationModeChange()\r\n\t// {\r\n\t// \tif (PrecipitationMode == _lastPrecipitationMode) return;\r\n\r\n\t// \t// Only host spawns networked objects\r\n\r\n\t// \t// Handle mode transitions\r\n\t// \tif (_lastPrecipitationMode == PrecipitationModeEnumeration.Rain)\r\n\t// \t{\r\n\t// \t\tTransitionOutRain();\r\n\t// \t}\r\n\t// \telse if (_lastPrecipitationMode == PrecipitationModeEnumeration.Snow)\r\n\t// \t{\r\n\t// \t\tTransitionOutSnow();\r\n\t// \t}\r\n\r\n\t// \t// Spawn new precipitation if not None\r\n\t// \tif (PrecipitationMode == PrecipitationModeEnumeration.Rain)\r\n\t// \t{\r\n\t// \t\tSpawnRain();\r\n\t// \t}\r\n\t// \telse if (PrecipitationMode == PrecipitationModeEnumeration.Snow)\r\n\t// \t{\r\n\t// \t\tSpawnSnow();\r\n\t// \t}\r\n\r\n\t// \t_lastPrecipitationMode = PrecipitationMode;\r\n\t// }\r\n\r\n\t// private void SpawnRain()\r\n\t// {\r\n\t// \t// Reset transition state to trigger fresh lerp\r\n\t// \t_lastPrecipitationIntensityValue = -1f;\r\n\t// \t_lastPrecipitationIntensityValueForParticles = -1f;\r\n\r\n\t// \t// If rain instance already exists (transitioning out), reactivate it\r\n\t// \tif (_precipitationControllerRain.IsValid())\r\n\t// \t{\r\n\t// \t\t_activePrecipitationControllerRain = true;\r\n\t// \t\treturn;\r\n\t// \t}\r\n\r\n\t// \tif (!PrecipitationController.IsValid()) return;\r\n\r\n\t// \tvar clonedGameObject = PrecipitationController.Clone();\r\n\t// \tclonedGameObject.Name = \"Precipitation Controller (Rain)\";\r\n\r\n\t// \t_rainGameObject = clonedGameObject;\r\n\t// \t_particleEffectRain = clonedGameObject.GetComponent<ParticleEffect>();\r\n\t// \t_precipitationControllerRain = clonedGameObject.GetComponent<PrecipitationController>();\r\n\t// \t_precipitationControllerRainEmitter = clonedGameObject.GetComponent<PrecipitationControllerEmitter>();\r\n\r\n\t// \t_precipitationControllerRain.PrecipitationMode = AtmokineticAssets.PrecipitationController.PrecipitationModeEnumeration.Rain;\r\n\r\n\t// \t_particleEffectRain.MaxParticles = 0;\r\n\t// \t_precipitationControllerRainEmitter.Rate = 0;\r\n\t// \t_activePrecipitationControllerRain = true;\r\n\r\n\t// \t// Reset transition to ensure smooth lerp from current state\r\n\t// }\r\n\r\n\t// private void SpawnSnow()\r\n\t// {\r\n\t// \t// Reset transition state to trigger fresh lerp\r\n\t// \t_lastPrecipitationIntensityValue = -1f;\r\n\t// \t_lastPrecipitationIntensityValueForParticles = -1f;\r\n\r\n\t// \t// If snow instance already exists (transitioning out), reactivate it\r\n\t// \tif (_precipitationControllerSnow.IsValid())\r\n\t// \t{\r\n\t// \t\t_activePrecipitationControllerSnow = true;\r\n\t// \t\treturn;\r\n\t// \t}\r\n\r\n\t// \tif (!PrecipitationController.IsValid()) return;\r\n\r\n\t// \tvar clonedGameObject = PrecipitationController.Clone();\r\n\t// \tclonedGameObject.Name = \"Precipitation Controller (Snow)\";\r\n\r\n\t// \t_snowGameObject = clonedGameObject;\r\n\t// \t_particleEffectSnow = clonedGameObject.GetComponent<ParticleEffect>();\r\n\t// \t_precipitationControllerSnow = clonedGameObject.GetComponent<PrecipitationController>();\r\n\t// \t_precipitationControllerSnowEmitter = clonedGameObject.GetComponent<PrecipitationControllerEmitter>();\r\n\r\n\t// \t_precipitationControllerSnow.PrecipitationMode = AtmokineticAssets.PrecipitationController.PrecipitationModeEnumeration.Snow;\r\n\r\n\t// \t_particleEffectSnow.MaxParticles = 0;\r\n\t// \t_precipitationControllerSnowEmitter.Rate = 0;\r\n\t// \t_activePrecipitationControllerSnow = true;\r\n\r\n\t// \t// Reset transition to ensure smooth lerp from current state\r\n\t// }\r\n\r\n\t// private void TransitionOutRain() => _activePrecipitationControllerRain = false;\r\n\r\n\t// private void TransitionOutSnow() => _activePrecipitationControllerSnow = false;\r\n\t\r\n\t// public void PrecipitationEffects()\r\n\t// {\r\n\t// \tif (_precipitationControllerRain.IsValid())\r\n\t// \t{\r\n\t// \t\tif (_activePrecipitationControllerRain)\r\n\t// \t\t{\r\n\t// \t\t\tUpdatePrecipitationTransition(\r\n\t// \t\t\t\t_particleEffectRain,\r\n\t// \t\t\t\t_precipitationControllerRainEmitter,\r\n\t// \t\t\t\t_precipitationControllerRain.RainIntensityMaximumParticlesLight,\r\n\t// \t\t\t\t_precipitationControllerRain.RainIntensityRateLight,\r\n\t// \t\t\t\t_precipitationControllerRain.RainIntensityMaximumParticlesModerate,\r\n\t// \t\t\t\t_precipitationControllerRain.RainIntensityRateModerate,\r\n\t// \t\t\t\t_precipitationControllerRain.RainIntensityMaximumParticlesHeavy,\r\n\t// \t\t\t\t_precipitationControllerRain.RainIntensityRateHeavy,\r\n\t// \t\t\t\t_precipitationControllerRain.RainIntensityMaximumParticlesExtreme,\r\n\t// \t\t\t\t_precipitationControllerRain.RainIntensityRateExtreme,\r\n\t// \t\t\t\t_precipitationControllerRain.RainParticleTransition\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\tTransitionPrecipitationToZero(_particleEffectRain, _precipitationControllerRainEmitter, _precipitationControllerRain.RainParticleTransition);\r\n\t// \t\t}\r\n\t// \t}\r\n\r\n\t// \tif (_precipitationControllerSnow.IsValid())\r\n\t// \t{\r\n\t// \t\tif (_activePrecipitationControllerSnow)\r\n\t// \t\t{\r\n\t// \t\t\tUpdatePrecipitationTransition(\r\n\t// \t\t\t\t_particleEffectSnow,\r\n\t// \t\t\t\t_precipitationControllerSnowEmitter,\r\n\t// \t\t\t\t_precipitationControllerSnow.SnowIntensityMaximumParticlesLight,\r\n\t// \t\t\t\t_precipitationControllerSnow.SnowIntensityRateLight,\r\n\t// \t\t\t\t_precipitationControllerSnow.SnowIntensityMaximumParticlesModerate,\r\n\t// \t\t\t\t_precipitationControllerSnow.SnowIntensityRateModerate,\r\n\t// \t\t\t\t_precipitationControllerSnow.SnowIntensityMaximumParticlesHeavy,\r\n\t// \t\t\t\t_precipitationControllerSnow.SnowIntensityRateHeavy,\r\n\t// \t\t\t\t_precipitationControllerSnow.SnowIntensityMaximumParticlesExtreme,\r\n\t// \t\t\t\t_precipitationControllerSnow.SnowIntensityRateExtreme,\r\n\t// \t\t\t\t_precipitationControllerSnow.SnowParticleTransition\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\tTransitionPrecipitationToZero(_particleEffectSnow, _precipitationControllerSnowEmitter, _precipitationControllerSnow.SnowParticleTransition);\r\n\t// \t\t}\r\n\t// \t}\r\n\r\n\t// \tif ( WaterPuddles != _lastWaterPuddlesValue || WaterLevel != _lastWaterLevelValue || PrecipitationIntensity != _lastPrecipitationIntensityValue)\r\n\t// \t{\r\n\t// \t\t_lastWaterPuddlesValue = WaterPuddles;\r\n\t// \t\t_lastWaterLevelValue = WaterLevel;\r\n\t// \t\t_lastPrecipitationIntensityValue = PrecipitationIntensity;\r\n\r\n\t// \t\tforeach ( var sceneModel in _cachedSceneObjects )\r\n\t// \t\t{\r\n\t// \t\t\tsceneModel.Attributes.Set( \"Water Puddles Accumulation\", WaterPuddles );\r\n\t// \t\t\tsceneModel.Attributes.Set( \"Water Plane Level\", WaterLevel );\r\n\t// \t\t\tsceneModel.Attributes.Set( \"Rain Ripples Intensity\", PrecipitationIntensity );\r\n\t// \t\t}\r\n\t// \t}\r\n\t// }\r\n\r\n\t// private void UpdatePrecipitationTransition(\r\n\t// \tParticleEffect particleEffect,\r\n\t// \tPrecipitationControllerEmitter emitter,\r\n\t// \tint lightMaxParticles, int lightRate,\r\n\t// \tint moderateMaxParticles, int moderateRate,\r\n\t// \tint heavyMaxParticles, int heavyRate,\r\n\t// \tint extremeMaxParticles, int extremeRate,\r\n\t// \tfloat transitionDuration)\r\n\t// {\r\n\t// \tif ( PrecipitationIntensity != _lastPrecipitationIntensityValueForParticles )\r\n\t// \t{\r\n\t// \t\t_lastPrecipitationIntensityValueForParticles = PrecipitationIntensity;\r\n\r\n\t// \t\t_startMaxParticles = particleEffect.MaxParticles;\r\n\t// \t\t_startRate = emitter.Rate.ConstantValue;\r\n\r\n\t// \t\tint intensity = (int)PrecipitationIntensity;\r\n\t// \t\t(_targetMaxParticles, _targetRate) = intensity switch\r\n\t// \t\t{\r\n\t// \t\t\t1 => (lightMaxParticles, lightRate),\r\n\t// \t\t\t2 => (moderateMaxParticles, moderateRate),\r\n\t// \t\t\t3 => (heavyMaxParticles, heavyRate),\r\n\t// \t\t\t4 => (extremeMaxParticles, extremeRate),\r\n\t// \t\t\t_ => (0, 0)\r\n\t// \t\t};\r\n\t// \t\t_transitionProgress = 0f;\r\n\t// \t}\r\n\r\n\t// \tif ( _transitionProgress < 1f )\r\n\t// \t{\r\n\t// \t\t_transitionProgress += Time.Delta / transitionDuration;\r\n\t// \t\tif ( _transitionProgress >= 1f )\r\n\t// \t\t{\r\n\t// \t\t\t_transitionProgress = 1f;\r\n\t// \t\t\tparticleEffect.MaxParticles = (int)_targetMaxParticles;\r\n\t// \t\t\temitter.Rate = _targetRate;\r\n\t// \t\t}\r\n\t// \t\telse\r\n\t// \t\t{\r\n\t// \t\t\tparticleEffect.MaxParticles = (int)(_startMaxParticles + (_targetMaxParticles - _startMaxParticles) * _transitionProgress);\r\n\t// \t\t\temitter.Rate = _startRate + (_targetRate - _startRate) * _transitionProgress;\r\n\t// \t\t}\r\n\t// \t}\r\n\t// }\r\n\r\n\t// private void TransitionPrecipitationToZero(ParticleEffect particleEffect, PrecipitationControllerEmitter emitter, float transitionDuration)\r\n\t// {\r\n\t// \tfloat step = Time.Delta / transitionDuration;\r\n\t// \tparticleEffect.MaxParticles = (int)Math.Max(0, particleEffect.MaxParticles - (particleEffect.MaxParticles * step));\r\n\t// \temitter.Rate = Math.Max(0, emitter.Rate.ConstantValue - (emitter.Rate.ConstantValue * step));\r\n\r\n\t// \tif (particleEffect.MaxParticles == 0)\r\n\t// \t{\r\n\t// \t\tif (_precipitationControllerRain.IsValid() && particleEffect == _particleEffectRain)\r\n\t// \t\t{\r\n\t// \t\t\t_rainGameObject?.Destroy();\r\n\t// \t\t\t_rainGameObject = null;\r\n\t// \t\t\t_precipitationControllerRain = null;\r\n\t// \t\t\t_particleEffectRain = null;\r\n\t// \t\t\t_precipitationControllerRainEmitter = null;\r\n\t// \t\t}\r\n\t// \t\telse if (_precipitationControllerSnow.IsValid() && particleEffect == _particleEffectSnow)\r\n\t// \t\t{\r\n\t// \t\t\t_snowGameObject?.Destroy();\r\n\t// \t\t\t_snowGameObject = null;\r\n\t// \t\t\t_precipitationControllerSnow = null;\r\n\t// \t\t\t_particleEffectSnow = null;\r\n\t// \t\t\t_precipitationControllerSnowEmitter = null;\r\n\t// \t\t}\r\n\t// \t}\r\n\t// }\r\n\t// public void TimeEffects()\r\n\t// {\r\n\t// \tif ( Hour != _lastHour)\r\n\t// \t{\r\n\t// \t\t_lastHour = Hour;\r\n\r\n\t// \t\tconst float sunriseStart = 5.6f;\r\n\t// \t\tconst float sunrisePeak  = 6.3f;\r\n\t// \t\tconst float sunriseEnd   = 7f;\r\n\t// \t\tconst float sunsetStart  = 17f;\r\n\t// \t\tconst float sunsetPeak   = 18.0f;\r\n\t// \t\tconst float sunsetEnd    = 18.4f;\r\n\t// \t\tconst float degreesPerHour = 15f;\r\n\t// \t\tconst float sunAngleOffset = 90f;\r\n\r\n\t// \t\tSun.WorldRotation = Rotation.From( (Hour * degreesPerHour) - sunAngleOffset, 0, 0 );\r\n\r\n\t// \t\tvar directionalLight = Sun.Components.Get<DirectionalLight>();\r\n\t// \t\tif ( directionalLight.IsValid() )\r\n\t// \t\t{\r\n\t// \t\t\tvar goldenHour = new Color( 1.0f, 0.45f, 0.1f );\r\n\t// \t\t\tColor sunColor;\r\n\r\n\t// \t\t\tif ( Hour >= sunsetEnd || Hour < sunriseStart )\r\n\t// \t\t\t{\r\n\t// \t\t\t\tsunColor = Color.Black;\r\n\t// \t\t\t}\r\n\t// \t\t\telse if ( Hour >= sunriseStart && Hour < sunrisePeak )\r\n\t// \t\t\t{\r\n\t// \t\t\t\tfloat time = (Hour - sunriseStart) / (sunrisePeak - sunriseStart);\r\n\t// \t\t\t\tsunColor = Color.Lerp( Color.Black, goldenHour, time );\r\n\t// \t\t\t}\r\n\t// \t\t\telse if ( Hour >= sunrisePeak && Hour < sunriseEnd )\r\n\t// \t\t\t{\r\n\t// \t\t\t\tfloat time = (Hour - sunrisePeak) / (sunriseEnd - sunrisePeak);\r\n\t// \t\t\t\tsunColor = Color.Lerp( goldenHour, Color.White, time );\r\n\t// \t\t\t}\r\n\t// \t\t\telse if ( Hour >= sunriseEnd && Hour < sunsetStart )\r\n\t// \t\t\t{\r\n\t// \t\t\t\tsunColor = Color.White;\r\n\t// \t\t\t}\r\n\t// \t\t\telse if ( Hour >= sunsetStart && Hour < sunsetPeak )\r\n\t// \t\t\t{\r\n\t// \t\t\t\tfloat time = (Hour - sunsetStart) / (sunsetPeak - sunsetStart);\r\n\t// \t\t\t\tsunColor = Color.Lerp( Color.White, goldenHour, time );\r\n\t// \t\t\t}\r\n\t// \t\t\telse\r\n\t// \t\t\t{\r\n\t// \t\t\t\tfloat time = (Hour - sunsetPeak) / (sunsetEnd - sunsetPeak);\r\n\t// \t\t\t\tsunColor = Color.Lerp( goldenHour, Color.Black, time );\r\n\t// \t\t\t}\r\n\r\n\t// \t\t\tdirectionalLight.LightColor = sunColor;\r\n\t// \t\t}\r\n\r\n\t// \t\tforeach ( var envmapProbe in _cachedEnvmapProbes )\r\n\t// \t\t{\r\n\t// \t\t\tif ( envmapProbe.IsValid() )\r\n\t// \t\t\t{\r\n\t// \t\t\t\tenvmapProbe.Dirty = true;\r\n\t// \t\t\t}\r\n\t// \t\t}\r\n\t// \t}\r\n\t// }\r\n}"
        },
        {
            "Ident": "ali3nsystems.lib-atmokinetic_assets",
            "Path": "Water/Done/Volume/Cube/WaterController.VolumeCube.cs",
            "FileName": "WaterController.VolumeCube.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 338612,
            "IsPrivate": false,
            "Code": "// namespace AtmokineticAssets;\r\n\r\n// [StructLayout(LayoutKind.Sequential)]\r\n// public struct WetnessVolumeCubeBuffer\r\n// {\r\n// \tpublic Vector3 VolumeCenter;\r\n// \tpublic Vector3 VolumeSize;\r\n// \tpublic float WetnessVolumeExtend;\r\n// \tpublic float WetnessVolumeFade;\r\n// \tpublic float WetnessVolumeSoftness;\r\n// }\r\n\r\n// public sealed partial class WaterController : Component\r\n// {\r\n// \t[Property, Title(\"Bounds Minimum (cm)\"), Feature(\"Wetness\"), Group(\"Properties\"), ShowIf( nameof(WetnessVolumeMode), WetnessVolumeModeEnumeration.Cube )]\r\n// \tpublic Vector3 WetnessVolumeCubeBoundsMinimum {get;set;} = new Vector3(-500, -500, -500);\r\n\r\n// \t[Property, Title(\"Bounds Maximum (cm)\"), Feature(\"Wetness\"), Group(\"Properties\"), ShowIf( nameof(WetnessVolumeMode), WetnessVolumeModeEnumeration.Cube )]\r\n// \tpublic Vector3 WetnessVolumeCubeBoundsMaximum {get;set;} = new Vector3( 500, 500, 500 );\r\n\r\n// \t[Property, Title( \"Enable Gizmo\" ), Feature(\"Wetness\"), Group(\"Properties\"), ShowIf( nameof(WetnessVolumeMode), WetnessVolumeModeEnumeration.Cube )]\r\n// \tpublic bool EnableWetnessVolumeCubeGizmos { get; set; } = true;\r\n\r\n// \tprivate static readonly List<WaterController> _wetnessVolumeCubeList = new();\r\n// \tprivate static GpuBuffer<WetnessVolumeCubeBuffer> _wetnessVolumeCubeBuffer;\r\n// \tprivate static int _currentWetnessVolumeCubeBuffer = 0;\r\n\r\n// \tpublic void EnableWetnessVolumeCubeBuffer()\r\n// \t{\r\n// \t\tif (!_wetnessVolumeCubeList.Contains(this))\r\n// \t\t\t_wetnessVolumeCubeList.Add(this);\r\n\r\n// \t\tif (_wetnessVolumeCubeBuffer == null)\r\n// \t\t{\r\n// \t\t\t_wetnessVolumeCubeBuffer = new GpuBuffer<WetnessVolumeCubeBuffer>(1);\r\n// \t\t\t_currentWetnessVolumeCubeBuffer = 1;\r\n// \t\t}\r\n// \t\tUpdateWetnessVolumeCubeBuffer();\r\n// \t}\r\n\r\n// \tpublic void DisableWetnessVolumeCubeBuffer()\r\n// \t{\r\n// \t\t_wetnessVolumeCubeList.Remove( this );\r\n// \t\tUpdateWetnessVolumeCubeBuffer();\r\n// \t}\r\n\r\n// \tpublic static void UpdateWetnessVolumeCubeBuffer()\r\n// \t{\r\n// \t\tif (_wetnessVolumeCubeList.Count == 0) return;\r\n\r\n// \t\tvar volumeBufferList = new List<WetnessVolumeCubeBuffer>();\r\n\r\n// \t\tforeach (var wetnessVolumeCube in _wetnessVolumeCubeList)\r\n// \t\t{\r\n// \t\t\tif (!wetnessVolumeCube.IsValid()) continue;\r\n\r\n// \t\t\t// Convert cm to inches\r\n// \t\t\tvar boundsMinimumInches = UnitConversion.CentimetersToInches(wetnessVolumeCube.WetnessVolumeCubeBoundsMinimum);\r\n// \t\t\tvar boundsMaximumInches = UnitConversion.CentimetersToInches(wetnessVolumeCube.WetnessVolumeCubeBoundsMaximum);\r\n\r\n// \t\t\t// Calculate world space center and size\r\n// \t\t\tVector3 localCenter = (boundsMinimumInches + boundsMaximumInches) * 0.5f;\r\n// \t\t\tVector3 worldCenter = wetnessVolumeCube.WorldPosition + localCenter;\r\n// \t\t\tVector3 size = boundsMaximumInches - boundsMinimumInches;\r\n\r\n// \t\t\tvolumeBufferList.Add(new WetnessVolumeCubeBuffer\r\n// \t\t\t{\r\n// \t\t\t\tVolumeCenter = worldCenter,\r\n// \t\t\t\tWetnessVolumeExtend = UnitConversion.CentimetersToInches( wetnessVolumeCube.WetnessVolumeExtend ),\r\n// \t\t\t\tVolumeSize = size,\r\n// \t\t\t\tWetnessVolumeFade = wetnessVolumeCube.WetnessVolumeFade,\r\n// \t\t\t\tWetnessVolumeSoftness = wetnessVolumeCube.WetnessVolumeSoftness,\r\n\r\n// \t\t\t});\r\n// \t\t}\r\n\r\n// \t\tif (_wetnessVolumeCubeBuffer == null || volumeBufferList.Count > _currentWetnessVolumeCubeBuffer)\r\n// \t\t{\r\n// \t\t\t_wetnessVolumeCubeBuffer = new GpuBuffer<WetnessVolumeCubeBuffer>(volumeBufferList.Count);\r\n// \t\t\t_currentWetnessVolumeCubeBuffer = volumeBufferList.Count;\r\n// \t\t}\r\n\r\n// \t\tif (volumeBufferList.Count > 0)\r\n// \t\t{\r\n// \t\t\t_wetnessVolumeCubeBuffer.SetData(volumeBufferList);\r\n// \t\t}\r\n\r\n// \t\tif (Game.ActiveScene?.SceneWorld != null)\r\n// \t\t{\r\n// \t\t\tvar sceneObjects = Game.ActiveScene.SceneWorld.SceneObjects\r\n// \t\t\t\t.Where(x => x != null && x.IsValid())\r\n// \t\t\t\t.ToList();\r\n\r\n// \t\t\tforeach (var sceneObject in sceneObjects)\r\n// \t\t\t{\r\n// \t\t\t\tif (sceneObject.IsValid())\r\n// \t\t\t\t{\r\n// \t\t\t\t\tsceneObject.Attributes.Set(\"WetnessWetnessVolumeCubeBuffer\", _wetnessVolumeCubeBuffer);\r\n// \t\t\t\t\tsceneObject.Attributes.Set(\"MaximumWetnessWetnessVolumeCubes\", volumeBufferList.Count);\r\n// \t\t\t\t}\r\n// \t\t\t}\r\n// \t\t}\r\n// \t}\r\n// }\r\n"
        },
        {
            "Ident": "ali3nsystems.lib-atmokinetic_assets",
            "Path": "Water/Reflection/WaterController.Reflection.cs",
            "FileName": "WaterController.Reflection.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 338612,
            "IsPrivate": false,
            "Code": "namespace AtmokineticAssets;\r\n\r\npublic sealed partial class WaterController : Component, Component.ExecuteInEditor\r\n{\r\n\tpublic enum WaterReflectionModeEnumeration\r\n\t{\r\n\t\tDynamic,\r\n\t\tStatic\r\n\t}\r\n\r\n\t[Property, Feature(\"Reflection\"), Header(\"Mode\")]\r\n\t[Sync]\r\n\tpublic WaterReflectionModeEnumeration WaterReflectionMode\r\n\t{\r\n\t\tget;\r\n\t\tset { field = value; ApplyWaterReflectionMode(); }\r\n\t} = WaterReflectionModeEnumeration.Dynamic;\r\n\r\n\tpublic enum WaterReflectionModeStaticModeEnumeration\r\n\t{\r\n\t\tPlanar,\r\n\t\tCubemap\r\n\t}\r\n\r\n\t[Property, Feature(\"Reflection\"), ShowIf( nameof(WaterReflectionMode), WaterReflectionModeEnumeration.Static)]\r\n\t[Sync]\r\n\tpublic WaterReflectionModeStaticModeEnumeration WaterReflectionModeStaticMode\r\n\t{\r\n\t\tget;\r\n\t\tset { field = value; ApplyWaterReflectionMode(); }\r\n\t} = WaterReflectionModeStaticModeEnumeration.Planar;\r\n\r\n\t// Mark the reflection mode dirty after its own settings change. Deliberately does\r\n\t// NOT apply immediately: these setters also fire from [Sync] snapshots while a\r\n\t// client is joining, before the scene camera exists - frustum work here NREs.\r\n\t// OnUpdate's change-check applies it on the next frame instead.\r\n\tprivate void ApplyWaterReflectionMode()\r\n\t{\r\n\t\t_lastWaterReflectionPlanarRendering = null;\r\n\t}\r\n\r\n\tpublic Vector3 WaterNormalAxis { get; set; } = Vector3.Up;\r\n\t\r\n}\r\n"
        },
        {
            "Ident": "ali3nsystems.lib-atmokinetic_assets",
            "Path": ".obj/__compiler_extra.cs",
            "FileName": "__compiler_extra.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 338612,
            "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\", \"Atmokinetic Assets\" )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"AddonIdent\", \"lib-atmokinetic_assets\" )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"OrgIdent\", \"ali3nsystems\" )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"Ident\", \"ali3nsystems.lib-atmokinetic_assets\" )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"EngineVersion\", \"28\" )]\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-08-03T22:07:53.7475400Z\" )]\r\n[assembly: global::System.Reflection.AssemblyVersion(\"0.0.111.0\")]\r\n[assembly: global::System.Reflection.AssemblyFileVersion(\"0.0.111.0\")]"
        },
        {
            "Ident": "ali3nsystems.lib-atmokinetic_assets",
            "Path": "Code/Camera/CameraController.Weather.WaterLine.cs",
            "FileName": "CameraController.Weather.WaterLine.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 338612,
            "IsPrivate": false,
            "Code": "namespace AtmokineticAssets;\r\n\r\npublic sealed partial class CameraController : BasePostProcess<CameraController>, Component.ExecuteInEditor\r\n{\r\n\tpublic GameObject WaterLineCameraGameObject;\r\n\tpublic CameraComponent WaterLineCameraComponent;\r\n\tpublic Texture WaterLineTexture;\r\n\r\n\tprivate void CheckWaterSubmersion()\r\n\t{\r\n\t\tvar corners = GetCameraNearPlaneCorners();\r\n\t\tif (corners == null)\r\n\t\t{\r\n\t\t\tIsCameraSubmerged = false;\r\n\t\t\tIsCameraFullySubmerged = false;\r\n\t\t\tif (WaterLineCameraGameObject.IsValid())\r\n\t\t\t{\r\n\t\t\t\tWaterController?.DestroyWaterLineMesh();\r\n\t\t\t\tDisableWaterLineRendering();\r\n\t\t\t}\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t// Cached by EnvironmentController on awake - no per-frame scene scan.\r\n\t\tvar waterControllers = EnvironmentController.CachedWaterControllers;\r\n\t\tint maxSubmergedCount = 0;\r\n\t\tWaterController activeWater = null;\r\n\r\n\t\tforeach (var water in waterControllers)\r\n\t\t{\r\n\t\t\tif (!water.IsValid()) continue;\r\n\r\n\t\t\tint submergedCount = CountCameraSubmergedCorners(corners, water);\r\n\t\t\tif (submergedCount > maxSubmergedCount)\r\n\t\t\t{\r\n\t\t\t\tmaxSubmergedCount = submergedCount;\r\n\t\t\t\tactiveWater = water;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tbool WasCameraFullySubmerged = IsCameraFullySubmerged;\r\n\t\tIsCameraSubmerged = maxSubmergedCount >= 1;\r\n\r\n\t\t// Use offset check for fully Submerged (only applies when already in water)\r\n\t\tint fullySubmergedCount = activeWater != null ? CountCameraFullySubmergedCorners(corners, activeWater) : 0;\r\n\t\tbool isNowFullySubmerged = fullySubmergedCount == 4;\r\n\t\tbool isNowPartiallySubmerged = IsCameraSubmerged && !isNowFullySubmerged;\r\n\r\n\t\t// Handle water controller change\r\n\t\tif (activeWater != WaterController)\r\n\t\t{\r\n\t\t\tWaterController?.DestroyWaterLineMesh();\r\n\t\t\tWaterController = activeWater;\r\n\t\t}\r\n\r\n\t\t// Water line cube (only when partially Submerged)\r\n\t\tif (isNowPartiallySubmerged && !WaterLineCameraGameObject.IsValid())\r\n\t\t{\r\n\t\t\tWaterController?.CreateWaterLineMesh();\r\n\t\t\tActivateWaterLineRendering();\r\n\t\t}\r\n\r\n\t\telse if (!isNowPartiallySubmerged && WaterLineCameraGameObject.IsValid())\r\n\t\t{\r\n\t\t\tWaterController?.DestroyWaterLineMesh();\r\n\t\t\tDisableWaterLineRendering();\r\n\t\t}\r\n\r\n\t\t// Delay IsCameraFullySubmerged off by one frame to prevent flash\r\n\t\tif (WasCameraFullySubmerged && isNowPartiallySubmerged)\r\n\t\t{\r\n\t\t\tif (_transitioningFromFullySubmerged)\r\n\t\t\t{\r\n\t\t\t\tIsCameraFullySubmerged = false;\r\n\t\t\t\t_transitioningFromFullySubmerged = false;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tIsCameraFullySubmerged = true;\r\n\t\t\t\t_transitioningFromFullySubmerged = true;\r\n\t\t\t}\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tIsCameraFullySubmerged = isNowFullySubmerged;\r\n\t\t\t_transitioningFromFullySubmerged = false;\r\n\t\t}\r\n\t}\r\n\r\n\tpublic void WaterLineShaderAttributes()\r\n\t{\r\n\t\tAttributes.Set(\"Water Line Size\", WaterController.WaterLineSize);\r\n\t\tAttributes.Set(\"Water Line Blur\", WaterController.WaterLineBlur);\r\n\t\tAttributes.Set(\"Water Line Color\", WaterController.WaterLineColor);\r\n\t}\r\n\r\n    private void ActivateWaterLineRendering()\r\n\t{\r\n\t\tCameraComponent.RenderExcludeTags.Add(\"water_line_mesh\");\r\n\r\n\t\tWaterLineCameraGameObject = new GameObject { Name = \"Water Line Camera\", Parent = GameObject };\r\n\t\t// Local-only render helper - never include it in network snapshots.\r\n\t\tWaterLineCameraGameObject.Flags |= GameObjectFlags.NotNetworked;\r\n\t\tWaterLineCameraGameObject.NetworkMode = NetworkMode.Never;\r\n\t\tWaterLineCameraComponent = WaterLineCameraGameObject.AddComponent<CameraComponent>();\r\n\t\tWaterLineCameraComponent.BackgroundColor = Color.Black;\r\n\t\tWaterLineCameraComponent.IsMainCamera = false;\r\n\t\tWaterLineCameraComponent.FovAxis = CameraComponent.FovAxis;\r\n\t\tWaterLineCameraComponent.FieldOfView = CameraComponent.FieldOfView;\r\n\t\tWaterLineCameraComponent.ZNear = CameraComponent.ZNear;\r\n\t\tWaterLineCameraComponent.ZFar = CameraComponent.ZFar;\r\n\t\tWaterLineCameraComponent.Priority = CameraComponent.Priority + 1;\r\n\t\tWaterLineCameraComponent.Orthographic = CameraComponent.Orthographic;\r\n\t\tWaterLineCameraComponent.OrthographicHeight = CameraComponent.OrthographicHeight;\r\n\t\tWaterLineCameraComponent.TargetEye = CameraComponent.TargetEye;\r\n\t\tWaterLineCameraComponent.Viewport = CameraComponent.Viewport;\r\n\t\tWaterLineCameraComponent.RenderTags.Add(\"water_line_mesh\");\r\n\t\tWaterLineCameraComponent.RenderExcludeTags.RemoveAll();\r\n\t\tWaterLineCameraComponent.EnablePostProcessing = false;\r\n\t}\r\n\r\n\tprivate void RenderWaterLineTexture()\r\n\t{\r\n\t\tWaterLineTexture = Texture.CreateRenderTarget(\"WaterLineRenderTexture\", ImageFormat.A8, Screen.Size, WaterLineTexture);\r\n\t\tWaterLineCameraComponent.RenderTarget = WaterLineTexture;\r\n\t\tAttributes.Set(\"WaterLineTexture\", WaterLineTexture);\r\n\t}\r\n\r\n\tprivate void DisableWaterLineRendering()\r\n\t{\r\n\t\tWaterLineCameraGameObject.Destroy();\r\n\t\tWaterLineCameraGameObject = null;\r\n\t\tWaterLineCameraComponent = null;\r\n\r\n\t\tWaterLineTexture.Dispose();\r\n\t\tWaterLineTexture = null;\r\n\r\n\t\tCameraComponent.RenderExcludeTags.Remove(\"water_line_mesh\");\r\n\t}\r\n}"
        }
    ]
}