rj-action-library/Runtime/Procedural/Scatter/Scatterer.cs

436 lines
9.8 KiB
C#

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<Vector3> _streamPositions = new List<Vector3>();
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<string,InstantiatedScatterPoint> _instantiatedPoints = new Dictionary<string, InstantiatedScatterPoint>();
MapList<Mesh,MassRenderer> _instancedMassRenderers = new MapList<Mesh, MassRenderer>();
Dictionary<Scatterer,int> _scattererIDs = new Dictionary<Scatterer, int>();
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<ScatterPoint>(), this );
X_createdPoints = points.Count;
X_instantiatedPoints = 0;
X_reusedPoints = 0;
X_remappedPoints = 0;
var usedPoints = new HashSet<string>();
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<PackedScene,InstantiatedScatterPoint>();
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<PackedScene,Transformable<SingleMaterialMesh>> _sceneMeshes = new MapList<PackedScene, Transformable<SingleMaterialMesh>>();
public void AddInstanced( ScatterPoint sp )
{
var packedScene = sp.scene;
if ( ! _sceneMeshes.ContainsKey( packedScene ) )
{
var node = sp.scene.Instantiate<Node>();
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<MassRenderer>();
_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>( "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<ScatterPoint> Scatter( List<ScatterPoint> 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<ScatterPoint> _Scatter( List<ScatterPoint> points, Scatterer root )
{
return points;
}
}
}