using System.Collections; using System.Collections.Generic; using Godot; using System; namespace Rokojori { [Tool] [GlobalClass, Icon("res://addons/rokojori_action_library/Icons/Scatterer.svg") ] public partial class Scatterer:Node3D { [ExportGroup("Update Behaviour")] [Export] public bool update = false; [Export] public bool updateAlways = false; [ExportGroup("Spawing")] [Export] public bool removeDiscarded = false; [Export] public Node3D[] streamSources; [Export] public float streamCullDistance = 100; [Export] public float streamFadeRange = 20; [Export] public int maxPoints = 1000000; [ExportGroup("Clean Up")] [Export] public Node3D[] containersToClearNodes; [Export] public bool clearContainers = false; [Export] public bool clearCache = false; [ExportGroup("Read Only")] [Export] public int X_createdPoints = 0; [Export] public int X_instantiatedPoints = 0; [Export] public int X_reusedPoints = 0; [Export] public int X_remappedPoints = 0; List _streamPositions = new List(); public override void _Process( double delta ) { if ( clearCache ) { clearCache = false; ClearCache(); } if ( clearContainers ) { clearContainers = false; ClearContainers(); } if ( ! ( update || updateAlways ) ) { return; } update = false; ScatterAndInstantiatePoints(); } public void ClearContainers() { Arrays.ForEach( containersToClearNodes, c => { Nodes.RemoveAndDeleteChildren( c ); } ); } class InstantiatedScatterPoint { public string hash; public ScatterPoint scatterPoint; public Node3D output; } Dictionary _instantiatedPoints = new Dictionary(); MapList _instancedMassRenderers = new MapList(); Dictionary _scattererIDs = new Dictionary(); public void ClearCache() { _instantiatedPoints.Clear(); _scattererIDs.Clear(); foreach ( var mmr in _instancedMassRenderers ) { mmr.Value.ForEach( mr => Nodes.RemoveAndDelete( mr ) ); } _instancedMassRenderers.Clear(); ClearContainers(); } int GetScattererID( Scatterer s ) { if ( ! _scattererIDs.ContainsKey( s ) ) { _scattererIDs[ s ] = _scattererIDs.Count; } return _scattererIDs[ s ]; } string GetScatterPointHash( ScatterPoint p ) { var scatterID = GetScattererID( p.creator ) + ":" + p.creatorID; return scatterID; } public void ScatterAndInstantiatePoints() { if ( _instantiatedPoints.Count == 0 ) { ClearContainers(); } foreach ( var mmr in _instancedMassRenderers ) { var list = mmr.Value; for ( int i = list.Count - 1; i >=0 ; i-- ) { list[ i ] = Nodes.EnsureValid( list[ i ] ); if ( list[ i ] == null ) { list.RemoveAt( i ); } else { list[ i ].Clear(); } } } var points = Scatter( new List(), this ); X_createdPoints = points.Count; X_instantiatedPoints = 0; X_reusedPoints = 0; X_remappedPoints = 0; var usedPoints = new HashSet(); points.RemoveAll( p => ! p.visible ); points.ForEach( p => { var hash = GetScatterPointHash( p ); if ( p.instanced ) { AddInstanced( p ); usedPoints.Add( hash ); return; } if ( ! CanReuse( hash, p ) ) { return; } var existing = _instantiatedPoints[ hash ]; existing.scatterPoint = p; existing.scatterPoint.UpdateInstantiated( existing.output ); usedPoints.Add( hash ); X_reusedPoints ++; return; } ); var unused = new MapList(); foreach ( var vk in _instantiatedPoints ) { var ip = vk.Value; if ( usedPoints.Contains( ip.hash ) ) { continue; } unused.Add( ip.scatterPoint.scene, ip ); } points.ForEach( p => { var hash = GetScatterPointHash( p ); if ( usedPoints.Contains( hash ) ) { return; } if ( unused.ContainsKey( p.scene ) ) { var unusedPoint = unused[ p.scene ].Find( ip => ip.scatterPoint.CanBeReusedBy( p ) ); if ( unusedPoint != null ) { unused.Remove( p.scene, unusedPoint ); unusedPoint.scatterPoint = p; unusedPoint.scatterPoint.UpdateInstantiated( unusedPoint.output ); usedPoints.Add( hash ); X_remappedPoints ++; return; } } var instantiatedOutput = p.Instantiate(); if ( instantiatedOutput == null ) { return; } var ip = new InstantiatedScatterPoint(); ip.hash = hash; ip.output = instantiatedOutput; ip.scatterPoint = p; if ( _instantiatedPoints.ContainsKey( hash ) ) { Nodes.RemoveAndDelete( _instantiatedPoints[ hash ].output ); } _instantiatedPoints[ hash ] = ip; usedPoints.Add( hash ); X_instantiatedPoints++; } ); Dictionaries.RemoveAll( _instantiatedPoints, ( k, v ) => { if ( usedPoints.Contains( k ) ) { return false; } Nodes.RemoveAndDelete( v.output ); return true; } ); foreach ( var mmr in _instancedMassRenderers ) { mmr.Value.ForEach( mr => mr.Create() ); } } MapList> _sceneMeshes = new MapList>(); public void AddInstanced( ScatterPoint sp ) { var packedScene = sp.scene; if ( ! _sceneMeshes.ContainsKey( packedScene ) ) { var node = sp.scene.Instantiate(); var meshesWithMaterials = MeshExtractor.ExtractMeshesInHierarchy( node ); _sceneMeshes[ packedScene ] = meshesWithMaterials; } var meshes = _sceneMeshes[ packedScene ]; meshes.ForEach( ( m )=> { var massRenderer = GetMassRenderer( sp, m.item ); var transform = sp.globalTransform3D; massRenderer.QueueObject( transform ); } ); } MassRenderer GetMassRenderer( ScatterPoint sp, SingleMaterialMesh mwm ) { if ( ! _instancedMassRenderers.ContainsKey( mwm.mesh ) ) { var massRenderer = new List(); _instancedMassRenderers[ mwm.mesh ] = massRenderer; } var mms =_instancedMassRenderers[ mwm.mesh ]; var mm = mms.Find( m => m.materialOveride == mwm.material ); if ( mm == null ) { var c = sp.parent.CreateChild( "MassRenderer" ); c.materialOveride = mwm.material; c.mesh = mwm.mesh; _instancedMassRenderers.Add( mwm.mesh, c ); return c; } return mm; } bool CanReuse( string hash, ScatterPoint sp ) { if ( ! _instantiatedPoints.ContainsKey( hash ) ) { return false; } var existing = _instantiatedPoints[ hash ]; var canBeReused = existing.scatterPoint.CanBeReusedBy( sp ); return canBeReused; } public List Scatter( List points, Scatterer root = null ) { if ( root == null ) { root = this; } var returnedPoints = _Scatter( points, root ); if ( removeDiscarded ) { returnedPoints.RemoveAll( p => ! p.visible ); } if ( root.streamSources != null && root.streamSources.Length > 0 ) { var fadeDistance = root.streamCullDistance - root.streamFadeRange; returnedPoints.RemoveAll( ( p ) => { for ( int s = 0; s < root.streamSources.Length; s++ ) { var distance = ( p.globalPosition - root.streamSources[ s ].GlobalPosition ).Length(); if ( distance < fadeDistance ) { return false; } var treshold = MathX.RemapClamped( distance, fadeDistance, root.streamCullDistance, 1, 0 ); var result = Noise.Perlin( p.globalPosition ) > treshold; if ( ! result ) { return false; } } return true; } ); } if ( returnedPoints.Count > root.maxPoints ) { returnedPoints.RemoveRange( root.maxPoints, returnedPoints.Count - root.maxPoints ); } return returnedPoints; } protected bool IsChildEnabled( Scatterer s, bool childrenNeedToBeVisible, bool childrenNeedToBeProcessing ) { var enabled = true; if ( childrenNeedToBeVisible ) { enabled = enabled && s.Visible; } if ( childrenNeedToBeProcessing ) { enabled = enabled && ( s.ProcessMode != ProcessModeEnum.Disabled ); } return enabled; } protected virtual List _Scatter( List points, Scatterer root ) { return points; } } }