436 lines
9.8 KiB
C#
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;
|
|
}
|
|
}
|
|
} |