Tree Update

This commit is contained in:
Josef 2025-06-27 07:12:53 +02:00
parent 26d24f9fe6
commit ef45d31599
43 changed files with 1881 additions and 69 deletions

View File

@ -5,7 +5,7 @@ using System;
namespace Rokojori namespace Rokojori
{ {
public class NodeState public static class NodeState
{ {
public static void Configure( Node n, bool processEnabled, bool inputEnabled, bool physicsEnabled, bool signalsEnabled, public static void Configure( Node n, bool processEnabled, bool inputEnabled, bool physicsEnabled, bool signalsEnabled,
Node.ProcessModeEnum processMode, bool visible ) Node.ProcessModeEnum processMode, bool visible )
@ -50,7 +50,7 @@ namespace Rokojori
Arrays.ForEach( nodes, n => Set( n, enabled ) ); Arrays.ForEach( nodes, n => Set( n, enabled ) );
} }
public static void Enable( Node n ) public static void Enable( this Node n )
{ {
Set( n, true ); Set( n, true );
} }
@ -60,7 +60,7 @@ namespace Rokojori
Set( n, true ); Set( n, true );
} }
public static void Disable( Node n ) public static void Disable( this Node n )
{ {
Set( n, false ); Set( n, false );
} }

View File

@ -9,6 +9,12 @@ namespace Rokojori
{ {
public static class Nodes public static class Nodes
{ {
public static NodePath GetNodePath( this Node node )
{
return node.GetPath();
}
public static T Get<T>( this Node node ) where T:Node public static T Get<T>( this Node node ) where T:Node
{ {
return GetAnyChild<T>( node ); return GetAnyChild<T>( node );
@ -161,7 +167,7 @@ namespace Rokojori
return list; return list;
} }
public static List<T> AllIn<T>( Node root, Func<T,bool> filter = null, bool includeRoot = true) where T:class public static List<T> GetAll<T>( this Node root, Func<T,bool> filter = null, bool includeRoot = true) where T:class
{ {
var list = new List<T>(); var list = new List<T>();
@ -481,10 +487,22 @@ namespace Rokojori
} }
} }
public static void SelfDestroy( this Node node )
{
var parent = node.GetParent();
if ( parent != null )
{
parent.RemoveChild( node );
}
node.QueueFree();
}
public static T GetDirectChild<T>( Node parent ) where T:Node
public static T GetDirectChild<T>( this Node parent ) where T:Node
{ {
if ( parent == null ) if ( parent == null )
{ {
@ -506,7 +524,7 @@ namespace Rokojori
return null; return null;
} }
public static List<T> GetDirectChildren<T>( Node parent ) where T:Node public static List<T> GetDirectChildren<T>( this Node parent ) where T:Node
{ {
if ( parent == null ) if ( parent == null )
{ {
@ -537,10 +555,6 @@ namespace Rokojori
return list; return list;
} }
public static void OnAllDirectChildren<T>( this Node parent, System.Action<T> action ) where T:Node
{
ForEachDirectChild<T>( parent, action );
}
public static int NumDirectChildrenOf<T>( this Node parent ) where T:Node public static int NumDirectChildrenOf<T>( this Node parent ) where T:Node
{ {
@ -566,6 +580,23 @@ namespace Rokojori
return typeIndex; return typeIndex;
} }
public static T GetLastChild<T>( this Node parent ) where T:Node
{
var numChildren = parent.GetChildCount();
for ( int i = numChildren - 1; i >= 0 ; i-- )
{
var child = parent.GetChild( i );
if ( child is T t)
{
return t;
}
}
return null;
}
public static T GetNthDirectChild<T>( this Node parent, int index ) where T:Node public static T GetNthDirectChild<T>( this Node parent, int index ) where T:Node
{ {
if ( parent == null ) if ( parent == null )
@ -596,7 +627,7 @@ namespace Rokojori
} }
public static void ForEachDirectChild<T>( Node parent, System.Action<T> action ) where T:Node public static void ForEachDirectChild<T>( this Node parent, System.Action<T> action ) where T:Node
{ {
if ( parent == null || action == null ) if ( parent == null || action == null )
{ {

View File

@ -0,0 +1,28 @@
using System.Collections;
using System.Collections.Generic;
using System;
namespace Rokojori
{
public class CustomTreeWalker<N>:TreeWalker<N> where N:class
{
Func<N,N> _getParent;
Func<N,int,N> _childAt;
Func<N,int> _numChildren;
public CustomTreeWalker( Func<N,N> getParent, Func<N,int,N> childAt, Func<N,int> numChildren )
{
_getParent = getParent;
_childAt = childAt;
_numChildren = numChildren;
}
public override N Parent( N node ){ return _getParent( node ); }
public override N ChildAt( N node, int index ){ return _childAt( node, index ); }
public override int NumChildren( N node ){ return _numChildren( node ); }
}
}

View File

@ -0,0 +1 @@
uid://bat8vplx8obqc

View File

@ -178,6 +178,121 @@ namespace Rokojori
return num <= 0 ? null : ChildAt( node, num - 1 ); return num <= 0 ? null : ChildAt( node, num - 1 );
} }
public void ForEachChild( N node, bool directChildrenOnly, Action<N> action )
{
if ( directChildrenOnly )
{
var numChildren = NumChildren( node );
for ( int i = 0; i < numChildren; i++ )
{
var child = ChildAt( node, i );
action( child );
}
}
else
{
Iterate( node, action, true );
}
}
public N FindDirectChild( N node, Predicate<N> selector )
{
return FindChild( node, true, selector );
}
public N FindAnyChild( N node, Predicate<N> selector )
{
return FindChild( node, false, selector );
}
public N FindChild( N node, bool directChildrenOnly, Predicate<N> selector )
{
if ( directChildrenOnly )
{
var numChildren = NumChildren( node );
for ( int i = 0; i < numChildren; i++ )
{
var child = ChildAt( node, i );
if ( selector( child ) )
{
return child;
}
}
}
else
{
return Find( node, selector, true );
}
return null;
}
public N GetDirectChildWithLowestValue( N node, Func<N, float> getValue )
{
return GetChildWithLowestValue( node, true, getValue );
}
public N GetAnyChildWithLowestValue( N node, Func<N, float> getValue )
{
return GetChildWithLowestValue( node, false, getValue );
}
public N GetChildWithLowestValue( N node, bool directChildrenOnly, Func<N, float> getValue )
{
var lowestValue = float.MaxValue;
N childWithLowestValue = null;
ForEachChild( node, directChildrenOnly,
( child )=>
{
var value = getValue( child );
if ( value < lowestValue )
{
lowestValue = value;
childWithLowestValue = child;
}
}
);
return childWithLowestValue;
}
public N GetDirectChildWithHighestValue( N node, Func<N, float> getValue )
{
return GetChildWithHighestValue( node, true, getValue );
}
public N GetAnyChildWithHighestValue( N node, Func<N, float> getValue )
{
return GetChildWithHighestValue( node, false, getValue );
}
public N GetChildWithHighestValue( N node, bool directChildrenOnly, Func<N, float> getValue )
{
var highestValue = -float.MaxValue;
N childWithHighestValue = null;
ForEachChild( node, directChildrenOnly,
( child )=>
{
var value = getValue( child );
if ( value > highestValue )
{
highestValue = value;
childWithHighestValue = child;
}
}
);
return childWithHighestValue;
}
public N NextNode( N node ) public N NextNode( N node )
{ {

View File

@ -27,13 +27,20 @@ namespace Rokojori
[Export] [Export]
public Node3D graphics; public Node3D graphics;
[Export] [Export]
public Smoothing rotationSmoothing; public Smoothing rotationSmoothing;
[Export] [Export]
public Smoothing positionSmoothing; public Smoothing positionSmoothing;
[Export]
public Node3D groundedTransform;
[Export]
public float lastGroundedHeight = 0;
public float delta = 0; public float delta = 0;
@ -54,6 +61,17 @@ namespace Rokojori
graphics.GlobalPosition = Smoothing.Apply( positionSmoothing, body.GlobalPosition, this.delta ); graphics.GlobalPosition = Smoothing.Apply( positionSmoothing, body.GlobalPosition, this.delta );
graphics.SetGlobalQuaternion( Smoothing.Apply( rotationSmoothing, body.GetGlobalQuaternion(), this.delta ) ); graphics.SetGlobalQuaternion( Smoothing.Apply( rotationSmoothing, body.GetGlobalQuaternion(), this.delta ) );
if ( body.IsOnFloor() )
{
lastGroundedHeight = graphics.GlobalPosition.Y;
}
var grounedPosition = graphics.GlobalPosition;
grounedPosition.Y = lastGroundedHeight;
groundedTransform.GlobalPosition = grounedPosition;
groundedTransform.SetGlobalQuaternion( graphics.GetGlobalQuaternion() );
// Pose.CopyTo( body, graphics ); // Pose.CopyTo( body, graphics );
} }

View File

@ -50,7 +50,7 @@ namespace Rokojori
this.LogInfo( "Grid Created", grid ); this.LogInfo( "Grid Created", grid );
var instances = Nodes.AllIn<MeshInstance3D>( source ); var instances = Nodes.GetAll<MeshInstance3D>( source );
this.LogInfo( "Grabbed instances", instances.Count ); this.LogInfo( "Grabbed instances", instances.Count );

View File

@ -31,6 +31,17 @@ namespace Rokojori
CreateRootCells(); CreateRootCells();
}
public OcTree( Vector3 center, float rootCellSize, int maxDepth )
{
this._min = center - Vector3.One * rootCellSize / 2f;
this._max = center + Vector3.One * rootCellSize / 2f;
this._rootCellSize = rootCellSize;
this._maxDepth = maxDepth;
CreateRootCells();
} }
public void SetCombiner( Func<List<T>,List<T>> combinePoints ) public void SetCombiner( Func<List<T>,List<T>> combinePoints )
@ -74,6 +85,40 @@ namespace Rokojori
return result; return result;
} }
public List<T> GetInsideBox( Box3 box, List<T> list = null )
{
list = list == null ? new List<T>() : list;
GetInsideBoxWithDuplicates( box, list );
var set = new HashSet<T>( [ .. list ] );
return set.ToList<T>();
}
public List<T> GetInsideBoxWithDuplicates( Box3 box, List<T> list = null )
{
list = list == null ? new List<T>() : list;
var rootIndexMin = PositionToRootIndex( box.min );
var rootIndexMax = PositionToRootIndex( box.max );
for ( int x = rootIndexMin.X; x <= rootIndexMax.X; x++ )
{
for ( int y = rootIndexMin.Y; y <= rootIndexMax.Y; y++ )
{
for ( int z = rootIndexMin.Z; z <= rootIndexMax.Z; z++ )
{
var rootIndex = new Vector3I( x, y, z );
var rootCell = GetRootCellByRootIndex( rootIndex );
rootCell.GetInsideBox( box, list );
}
}
}
return list;
}
public bool Insert( T data ) public bool Insert( T data )
{ {
var rootCell = GetRootCell( _getPosition( data ) ); var rootCell = GetRootCell( _getPosition( data ) );
@ -91,6 +136,26 @@ namespace Rokojori
return rootCell.Insert( data ); return rootCell.Insert( data );
} }
public void BoxInsert( T data, Box3 box )
{
var rootIndexMin = PositionToRootIndex( box.min );
var rootIndexMax = PositionToRootIndex( box.max );
for ( int x = rootIndexMin.X; x <= rootIndexMax.X; x++ )
{
for ( int y = rootIndexMin.Y; y <= rootIndexMax.Y; y++ )
{
for ( int z = rootIndexMin.Z; z <= rootIndexMax.Z; z++ )
{
var rootIndex = new Vector3I( x, y, z );
var rootCell = GetRootCellByRootIndex( rootIndex );
rootCell.BoxInsert( data, box );
}
}
}
}
public int GetLevelForSize( float size ) public int GetLevelForSize( float size )
{ {
var level = 0; var level = 0;

View File

@ -49,6 +49,27 @@ namespace Rokojori
public int rootCellIndex => _rootCellIndex; public int rootCellIndex => _rootCellIndex;
public void GetInsideBox( Box3 box, List<T> list )
{
if ( ! _box.Overlaps( box ) )
{
return;
}
if ( _values != null )
{
list.AddRange( _values );
}
if ( _cells == null )
{
return;
}
_cells.ForEach( c => c.GetInsideBox( box, list ) );
}
public static OcTreeCell<T> Create( OcTree<T> tree, Vector3 min, Vector3 max, int rootIndex ) public static OcTreeCell<T> Create( OcTree<T> tree, Vector3 min, Vector3 max, int rootIndex )
{ {
var cell = new OcTreeCell<T>(); var cell = new OcTreeCell<T>();
@ -83,6 +104,32 @@ namespace Rokojori
return _center + halfSize * polarUVW; return _center + halfSize * polarUVW;
} }
public void BoxInsert( T data, Box3 box )
{
if ( ! _box.Overlaps( box ) )
{
return;
}
if ( isLeaf )
{
if ( _values == null )
{
_values = new List<T>();
}
values.Add( data );
return;
}
Nest();
cells.ForEach( c => c.BoxInsert( data, box ) );
return;
}
public bool Insert( T data ) public bool Insert( T data )
{ {
if ( isLeaf ) if ( isLeaf )

View File

@ -100,6 +100,39 @@ namespace Rokojori
return point; return point;
} }
public bool Overlaps( Box3 other )
{
return Overlap3D.Has( this, other );
}
public float DistanceTo( Sphere a )
{
return Mathf.Sqrt( SquareDistanceTo( a ) );
}
public float SquareDistanceTo( Sphere a )
{
var squareDistance = 0.0f;
for ( int i = 0; i < 3; i++ )
{
var dimension = center[ i ] ;
if ( dimension < min[ i ] )
{
var dimensionDistance = dimension - min[ i ];
squareDistance += dimensionDistance * dimensionDistance;
}
else if ( dimension > max[ i ] )
{
var distance = dimension - max[ i ];
squareDistance += distance * distance;
}
}
return squareDistance;
}
public bool ContainsPoint( Vector3 point ) public bool ContainsPoint( Vector3 point )
{ {
if ( ! Range.Contains( min.X, max.X, point.X ) ) if ( ! Range.Contains( min.X, max.X, point.X ) )

View File

@ -38,6 +38,13 @@ namespace Rokojori
return Mathf.Max( 0, lineDistance - radius ); return Mathf.Max( 0, lineDistance - radius );
} }
public float DistanceTo( Capsule3 other )
{
var lineDistance = centerLine.DistanceToLine( other.centerLine );
return Mathf.Max( 0, lineDistance - ( radius + other.radius ) );
}
} }
} }

View File

@ -28,6 +28,14 @@ namespace Rokojori
this.end = end; this.end = end;
} }
public Box3 GetBox( float expansion = 0 )
{
var min = start.Min( end ) - Vector3.One * expansion;
var max = start.Max( end ) + Vector3.One * expansion;
return Box3.Create( min, max );
}
public override Vector3 PositionAt( float t ) public override Vector3 PositionAt( float t )
{ {
return start.Lerp( end, t ); return start.Lerp( end, t );

View File

@ -0,0 +1,44 @@
using Godot;
using System.Collections;
using System.Collections.Generic;
namespace Rokojori
{
public class Overlap3D
{
public static bool Has( Box3 a, Box3 b )
{
if ( ! Range.Overlap( a.min.Y, a.max.Y, b.min.Y, b.max.Y ) )
{
return false;
}
if ( ! Range.Overlap( a.min.X, a.max.X, b.min.X, b.max.X ) )
{
return false;
}
if ( ! Range.Overlap( a.min.Z, a.max.Z, b.min.Z, b.max.Z ) )
{
return false;
}
return true;
}
public static bool Has( Sphere a, Sphere b )
{
return a.IntersectsSphere( b );
}
public static bool Has( Box3 a, Sphere b )
{
return a.SquareDistanceTo( b ) <= 0;
}
public static bool Has( Sphere a, Box3 b )
{
return b.SquareDistanceTo( a ) <= 0;
}
}
}

View File

@ -0,0 +1 @@
uid://b8oy166pmufgv

View File

@ -18,6 +18,15 @@ namespace Rokojori
} }
} }
public Pose Clone()
{
var p =new Pose();
p._rotation = _rotation;
p._position = _position;
return p;
}
public void ApplyTwist( float twist ) public void ApplyTwist( float twist )
{ {
rotation = rotation * Math3D.RotateZ( twist ); rotation = rotation * Math3D.RotateZ( twist );

View File

@ -264,6 +264,7 @@ namespace Rokojori
return center / points.Length; return center / points.Length;
} }
public static Vector3 Center<N>( List<N> points ) where N:Node3D public static Vector3 Center<N>( List<N> points ) where N:Node3D
{ {
var center = Vector3.Zero; var center = Vector3.Zero;
@ -600,6 +601,11 @@ namespace Rokojori
return new Vector4( q.X, q.Y, q.Z, q.W ); return new Vector4( q.X, q.Y, q.Z, q.W );
} }
public static Quaternion FromEulerDegrees( Vector3 eulerYXZ )
{
return Quaternion.FromEuler( eulerYXZ * MathX.DegreesToRadians );
}
public static Quaternion RotateX( float radians ) public static Quaternion RotateX( float radians )
{ {
if ( radians == 0 ) { return Quaternion.Identity; } if ( radians == 0 ) { return Quaternion.Identity; }
@ -938,6 +944,18 @@ namespace Rokojori
return amount <= 0.5 ? a : b; return amount <= 0.5 ? a : b;
} }
public static Box3 ToBox3( this Aabb aabb )
{
return (Box3) aabb;
}
public static Box3 GetWorldBox( this Node3D node, bool onlyVisible = true )
{
var aabb = GetWorldBounds( node, onlyVisible );
return aabb == null ? null : ( (Aabb)aabb).ToBox3();
}
public static Aabb? GetWorldBounds( this Node3D node, bool onlyVisible = true ) public static Aabb? GetWorldBounds( this Node3D node, bool onlyVisible = true )
{ {
return GetWorldBoundsFrom( node, onlyVisible ); return GetWorldBoundsFrom( node, onlyVisible );

View File

@ -10,6 +10,9 @@ namespace Rokojori
{ {
public const float fps120Delta = 1/120f; public const float fps120Delta = 1/120f;
public const float DegreesToRadians = Mathf.Pi / 180f;
public const float RadiansToDegrees = 180f / Mathf.Pi;
static Dictionary<int,int> factorialLookUp = new Dictionary<int, int>(); static Dictionary<int,int> factorialLookUp = new Dictionary<int, int>();
public static int Factorial( int i ) public static int Factorial( int i )
@ -198,8 +201,7 @@ namespace Rokojori
} }
public const float DegreesToRadians = Mathf.Pi / 180f;
public const float RadiansToDegrees = 180f / Mathf.Pi;
public static float AngleDelta( float degreesA, float degreesB) public static float AngleDelta( float degreesA, float degreesB)
{ {
@ -426,6 +428,30 @@ namespace Rokojori
return value; return value;
} }
public static Curve CurveFromPoints( params float[] points )
{
var curve = new Curve();
for ( int i = 0; i < points.Length; i++ )
{
var t = (float) i / (float) ( points.Length - 1f );
curve.AddPoint( new Vector2( t, points[ i ] ) );
if ( i != 0 )
{
curve.SetPointLeftMode( i, Godot.Curve.TangentMode.Linear );
}
if ( i != points.Length - 1 )
{
curve.SetPointRightMode( i, Godot.Curve.TangentMode.Linear );
}
}
return curve;
}
public static Curve Curve( float y0, float y1, float minValue = 0, float maxValue = 1 ) public static Curve Curve( float y0, float y1, float minValue = 0, float maxValue = 1 )
{ {
var curve = new Curve(); var curve = new Curve();

View File

@ -39,7 +39,7 @@ namespace Rokojori
return true; return true;
} }
if ( Contains( min ) || Contains( max ) ) if ( Contains( other.min ) || Contains( other.max ) )
{ {
return true; return true;
} }
@ -95,6 +95,25 @@ namespace Rokojori
return min <= value && value < max; return min <= value && value < max;
} }
public static bool Overlap( float minA, float maxA, float minB, float maxB )
{
if ( maxB < minA ) { return false; }
if ( minB > maxA ) { return false; }
if ( Contains( minB, maxB, minA ) || Contains( minB, maxB, maxA ) )
{
return true;
}
if ( Contains( minA, maxA, minB ) || Contains( minA, maxA, maxB ) )
{
return true;
}
return false;
}
} }

View File

@ -0,0 +1,71 @@
using System.Collections;
using System.Collections.Generic;
using Godot;
using System;
using System.Threading.Tasks;
namespace Rokojori
{
[Tool]
[GlobalClass]
public partial class TreeBranch:Resource
{
[Export]
public NodePath spline;
[Export]
public Vector3 start;
[Export]
public Vector3 end;
[Export]
public int startSeed;
[Export]
public Curve curve;
public Spline GetSpline( Node node )
{
return node.GetNode( spline ) as Spline;
}
[Export]
public int index = -1;
[Export]
public int parent = -1;
[Export]
public int level = -1;
[Export]
public int[] children = [];
[Export]
public float radius = 0;
[Export]
public float radius2 = 0;
[Export]
public float radius3 = 0;
[Export]
public int numRadi = 1;
[Export]
public float height = 0;
[Export]
public float branchPosition = 0;
public Line3 GetLine3()
{
return new Line3( start, end );
}
}
}

View File

@ -0,0 +1 @@
uid://qfl4okpj3plh

View File

@ -0,0 +1,737 @@
using System.Collections;
using System.Collections.Generic;
using Godot;
using System;
using System.Threading.Tasks;
using System.Linq;
namespace Rokojori
{
[Tool]
[GlobalClass, Icon("res://addons/rokojori_action_library/Icons/Scatterer.svg") ]
public partial class TreeGenerator:Node3D
{
[Export]
public float rootRadius;
[Export]
public float rootHeight;
[Export]
public Curve branchPosition;
[Export]
public Curve levelToChildBranches;
[Export]
public Curve levelToSubdivisionNoise;
[Export]
public Curve subdivisionNoiseRange;
[Export]
public int maxLevel = 4;
[Export]
public Curve radiusScale;
[Export]
public Curve heightScale;
[Export]
public Curve siblingRotationVariance;
[Export]
public Curve rotationPerLevel;
[Export]
public Curve rotationVariation;
[Export]
public int radialSegments = 8;
[Export]
public int minRadialSegements = 3;
[Export]
public int maxRadialSegments = 64;
[Export]
public Curve radialSegementsMultiplier;
[Export]
public bool avoidClusters = true;
[Export]
public float minClusterDistance = 1;
[Export]
public float noise = 0.1f;
[Export]
public TubeGeometrySettings branchSettings;
[Export]
public TubeShape[] shapes;
[Export]
public Material branchMaterial;
[Export]
public Material leavesMaterial;
[Export]
public int leavesRows = 4;
[Export]
public int leavesColumns = 4;
[Export]
public bool hideStructureWhenMeshWasBuild = true;
[Export]
public int seed = 1998;
[Export]
public bool randomizeSeed = false;
[ExportToolButton( "Generate Branches Structure" )]
public Callable GenerateBranchesButton => Callable.From(
( )=>
{
GenerateBranches();
}
);
[ExportToolButton( "Generate Branches Mesh" )]
public Callable GenerateMeshButton => Callable.From(
( )=>
{
GenerateMesh();
}
);
[ExportToolButton( "Generate Branches And Mesh" )]
public Callable GenerateBranchesMeshButton => Callable.From(
( )=>
{
GenerateBranchesAndMesh();
}
);
[Export]
bool _generating = false;
[Export]
public TreeBranch[] branches = [];
List<TreeBranch> _branches = [];
SeedableRandomEngine random;
[Export]
public Node deselecter;
async Task GenerateBranchesAndMesh()
{
try
{
if ( useDebugSingleMeshGeneration )
{
EditorInterface.Singleton.GetSelection().Clear();
EditorInterface.Singleton.GetSelection().AddNode( deselecter );
await this.RequestNextFrame();
}
await GenerateBranches();
await GenerateMesh();
}
catch( System.Exception e )
{
this.LogError( e );
}
return;
}
[Export]
public bool useDebugSingleMeshGeneration = false;
async Task GenerateMesh()
{
var time = Async.StartTimer();
var mg = new MeshGeometry();
var meshInstances = this.GetDirectChildren<MeshInstance3D>();
meshInstances.ForEach( m => m.SelfDestroy() );
if ( useDebugSingleMeshGeneration )
{
this.GetDirectChildren<MeshInstance3D>().ForEach( m => m.SelfDestroy() );
}
else
{
this.GetAll<MeshInstance3D>().ForEach( m => m.SelfDestroy() );
}
for ( int i = 0; i < _branches.Count; i++ )
{
time = await Async.WaitIfExceeded( time, this, processTimePerFrame );
var branchMG = CreateBranchMesh( _branches[ i ] );
if ( useDebugSingleMeshGeneration )
{
var meshInstance3D = this.CreateChild<MeshInstance3D>( "Branch Mesh @" + _branches[ i ].index );
meshInstance3D.Mesh = branchMG.GenerateMesh();
meshInstance3D.SetSurfaceOverrideMaterial( 0, branchMaterial );
if ( i % 5 == 0 )
{
await this.RequestNextFrame();
}
}
else
{
mg.Add( branchMG );
}
}
var leavesMG = new MeshGeometry();
for ( int bb =0; bb < _branches.Count; bb++ )
{
time = await Async.WaitIfExceeded( time, this, processTimePerFrame );
var b = _branches[ bb ];
if ( b.level < leavesLevel )
{
continue;
}
var numLeaves = ( b.height / leafSize ) * random.Sample( leavesDensity );
var curve = b.GetSpline( this ).GetCurve();
for ( int i = 0; i < numLeaves; i++ )
{
var t = i / ( numLeaves - 1f );
var leafGeometry = new MeshGeometry();
var index = random.IntegerExclusive( leavesRows * leavesColumns );
var indexX = index % leavesColumns;
var indexY = index / leavesColumns;
var uvStart = new Vector2( indexX, indexY ) / new Vector2( leavesColumns, leavesRows );
var uvEnd = uvStart + Vector2.One / new Vector2( leavesColumns, leavesRows );
var p = curve.PoseAt( t );
var rx = random.Range( 0, 360 );
var ry = random.Range( 0, 360 );
var rz = random.Range( 0, 360 );
var leafSizeCurrent = leafSize * random.Sample( leafSizeScaleVariance, 1, 1 );
leafGeometry.AddQuad( Math3D.FromEulerDegrees( new Vector3( rx, ry, rz ) ),
leafSizeCurrent, uvStart, uvEnd, p.position );
leafGeometry.BlendNormalsOverY( Vector3.Up, leavesNormalBlending, leavesStartBlend, leavesEndBlend, leavesBlendCurve );
if ( useDebugSingleMeshGeneration )
{
time = await Async.WaitIfExceeded( time, processTimePerFrame );
var leavesInstance = this.CreateChild<MeshInstance3D>( "Leaves Mesh @" + b.index + "." + i );
leavesInstance.Mesh = leafGeometry.GenerateMesh();
leavesInstance.SetSurfaceOverrideMaterial( 0, leavesMaterial );
}
else
{
leavesMG.Add( leafGeometry );
}
}
}
if ( ! useDebugSingleMeshGeneration )
{
var leavesInstance = this.CreateChild<MeshInstance3D>( "Leaves Mesh" );
leavesInstance.Mesh = leavesMG.GenerateMesh();
leavesInstance.SetSurfaceOverrideMaterial( 0, leavesMaterial );
var meshInstance3D = this.CreateChild<MeshInstance3D>( "Tree Mesh");
meshInstance3D.Mesh = mg.GenerateMesh();
meshInstance3D.SetSurfaceOverrideMaterial( 0, branchMaterial );
}
if ( hideStructureWhenMeshWasBuild )
{
structure.Disable();
}
numTriangles = mg.numTriangles;
return;
}
[Export]
public int leavesLevel = 3;
[Export]
public Vector3 leavesEulerRotationPre =Vector3.Zero;
[Export]
public Vector3 leavesEulerRotationPost =Vector3.Zero;
[Export]
public float leafSize = 1;
[Export]
public Curve leafSizeScaleVariance;
[Export]
public Curve leavesDensity;
[Export]
public float leavesNormalBlending = 0.5f;
[Export]
public Curve leavesBlendCurve;
[Export]
public float leavesStartBlend = 4;
[Export]
public float leavesEndBlend = 8;
[Export]
public int numTriangles = 0;
[Export]
public float processTimePerFrame = 1f/80f;
[Export]
public float smallestRadius = 0.02f;
float SmallestChildRadius( TreeBranch tb )
{
var radius = smallestRadius;
if ( tb.children.Length > 0 )
{
radius = _branches[ tb.children[ 0 ] ].radius;
}
for ( int i = 1; i < tb.children.Length; i++ )
{
var childIndex = tb.children[ i ];
var childTB = _branches[ childIndex ];
radius = Mathf.Min( radius, childTB.radius );
}
return radius;
}
MeshGeometry CreateBranchMesh( TreeBranch tb )
{
var tubeGeometry = new TubeGeometry();
tubeGeometry.curve = tb.GetSpline( this ).GetCurve();
var normalizedLevel = tb.level / (float)( maxLevel );
var levelSegments = radialSegementsMultiplier.Sample( normalizedLevel );
branchSettings.radialSegments = Mathf.RoundToInt( radialSegments * levelSegments );
tubeGeometry.settings = branchSettings;
tubeGeometry.shapes = shapes;
branchSettings.radius = 1.0f;
if ( tb.numRadi == 3 )
{
tb.curve = MathX.CurveFromPoints( tb.radius, tb.radius2, tb.radius3 );
branchSettings.radiusSizeCurve = tb.curve;
}
else
{
branchSettings.radiusSizeCurve = MathX.Curve( tb.radius, SmallestChildRadius( tb ) );
}
var mesh = tubeGeometry.CreateMesh();
// mesh.SmoothNormals();
return mesh;
}
[Export]
public Node3D structure;
OcTree<TreeBranch> ocTree;
CustomTreeWalker<TreeBranch> walker;
async Task GenerateBranches()
{
if ( walker == null )
{
walker = new CustomTreeWalker<TreeBranch>(
tb => _branches[ tb.parent ],
( tb, childIndex ) => _branches[ childIndex ],
tb => tb.children.Count()
);
}
if ( randomizeSeed )
{
seed = GodotRandom.Get().IntegerInclusive( 0, 10000 );
}
branches = [];
_branches.Clear();
this.DestroyChildren();
ocTree = new OcTree<TreeBranch>( GlobalPosition, rootHeight * 3 * maxLevel, 10 );
structure = this.CreateChild<Node3D>( "Tree Structure" );
random = LCG.WithSeed( seed );
branches = _branches.ToArray();
var nextParents = new List<TreeBranch>();
var root = GenerateBranch( -1, 0, 0 );
nextParents.Add( root );
var time = Async.StartTimer();
maxChecksFull = 0;
for ( int i = 0; i < maxLevel; i++ )
{
var parents = nextParents;
nextParents = new List<TreeBranch>();
var normalizedLevel = ( ( (float) i ) / (float) ( maxLevel - 1f ) );
// this.LogInfo( "Level", i, normalizedLevel, "Parents", parents.Count );
for ( int j =0 ; j < parents.Count; j++ )
{
var p = parents[ j ];
random.SetSeed( p.startSeed + 1234 );
var numKids = Mathf.RoundToInt( levelToChildBranches.Sample( normalizedLevel ) );
// this.LogInfo( i, "Parent", j, "Index:", p.index, "Kids:", numKids );
for ( int k = 0; k < numKids; k++ )
{
time = await Async.WaitIfExceeded( time, this, processTimePerFrame );
nextParents.Add( GenerateBranch( p.index, normalizedLevel, numKids ) );
}
// this.LogInfo( "Num Next", nextParents.Count );
}
}
for ( int i = 0; i < _branches.Count; i++ )
{
time = await Async.WaitIfExceeded( time, processTimePerFrame );
var p = _branches[ i ];
if ( p.children.Length == 0 )
{
continue;
}
var child = walker.GetDirectChildWithHighestValue( p, tb => tb.branchPosition );
if ( child == null || child.parent != p.index )
{
continue;
}
var spline = p.GetSpline( this );
var lastPoint = spline.GetLastChild<SplinePoint>();
if ( lastPoint == null )
{
this.LogInfo( "has no lastPoint:", p.index );
continue;
}
var childSpline = child.GetSpline( this );
var firstPoint = childSpline.GetChild<SplinePoint>();
var lastPoint2 = childSpline.GetLastChild<SplinePoint>();
p.radius2 = SmallestChildRadius( p );
p.radius3 = SmallestChildRadius( child ) * 0.5f;
p.numRadi = 3;
lastPoint.GlobalPosition = firstPoint.GlobalPosition;
lastPoint.SetGlobalQuaternion( firstPoint.GetGlobalQuaternion() );
var additional = spline.CreateChild<SplinePoint>();
additional.GlobalPosition = lastPoint2.GlobalPosition;
additional.SetGlobalQuaternion( lastPoint2.GetGlobalQuaternion() );
spline.ClearCurveCache();
spline.AutoOrientate();
spline.GetCurve();
}
if ( levelToSubdivisionNoise != null )
{
for ( int bb = 0; bb < _branches.Count; bb++ )
{
time = await Async.WaitIfExceeded( time, processTimePerFrame );
var b = _branches[ bb ];
var normalizedLevel = b.level / maxLevel;
var subdivisions = Mathf.Ceil( levelToSubdivisionNoise.Sample( normalizedLevel ) );
var spline = b.GetSpline( this );
var curve = spline.GetCurve();
var numPoints = curve.points.Count + subdivisions;
spline.DestroyChildren();
spline.ClearCurveCache();
if ( useDebugSingleMeshGeneration && bb % 5 == 0 )
{
await this.RequestNextFrame();
}
for ( int i = 0; i < numPoints; i++ )
{
time = await Async.WaitIfExceeded( time, processTimePerFrame );
var t = i / (numPoints - 1f );
var t2 = 1f - t;
var minT = Mathf.Min( t * 2f, t2 * 2f );
var p = curve.PoseAt( t );
var rr = random.Sample( subdivisionNoiseRange );
p.position += random.InSphere( rr * minT * b.height / (float)subdivisions );
var sp = spline.CreateChild<SplinePoint>();
sp.editorSplinePointSize = 0.001f;
p.Set( sp );
}
var ss = b.GetSpline( this );
ss.AutoOrientate();
}
}
branches = _branches.ToArray();
if ( hideStructureWhenMeshWasBuild )
{
structure.Disable();
}
return;
}
[Export]
int maxChecksFull = 0;
TreeBranch GenerateBranch( int parent, float normalizedLevel, int numSiblings )
{
TreeBranch parentBranch = null;
var childIndex = 0;
if ( parent != -1 )
{
parentBranch = _branches[ parent ];
childIndex = parentBranch.children.Length;
}
var treeBranch = new TreeBranch();
treeBranch.parent = parent;
treeBranch.level = parentBranch == null ? 0 : parentBranch.level + 1;
treeBranch.index = _branches.Count;
treeBranch.startSeed = seed * 100000 + treeBranch.level * 1000 + childIndex;
random.SetSeed( treeBranch.startSeed );
if ( parentBranch == null )
{
treeBranch.radius = rootRadius;
treeBranch.height = rootHeight;
}
else
{
treeBranch.radius = parentBranch.radius * random.Sample( radiusScale );
treeBranch.height = parentBranch.height * random.Sample( heightScale );
parentBranch.children = Arrays.Add( parentBranch.children, treeBranch.index );
}
treeBranch.branchPosition = random.SelectCurveWeights( branchPosition, 100 );
var spline = structure.CreateChild<Spline>( "Branch - " + treeBranch.level + " - " + childIndex);
treeBranch.spline = spline.GetNodePath();
if ( parentBranch == null )
{
var root = spline.CreateChild<SplinePoint>();
var end = spline.CreateChild<SplinePoint>();
root.editorSplinePointSize = 0.001f;
end.Position = new Vector3( 0, rootHeight, 0 ) + random.InSphere( noise );
spline.autoOrientationUp = Vector3.Back;
}
else
{
var parentSpline = parentBranch.GetSpline( this );
var parentCurve = parentSpline.GetCurve();
var rootPose = parentCurve.PoseAt( treeBranch.branchPosition );
var siblingRotation = 360f * ( childIndex / (float)numSiblings );
siblingRotation += random.Sample( siblingRotationVariance, 0, 0 );
rootPose.ApplyTwist( siblingRotation );
var rootRotationCenter = rotationPerLevel.Sample( normalizedLevel );
var rootRotationRange = rotationVariation == null ? 0 : random.Sample( rotationVariation );
rootPose.RotateAround( Math3D.RotateXDegrees( random.Range( rootRotationCenter - rootRotationRange, rootRotationCenter + rootRotationRange ) ), rootPose.position );
var endPose = rootPose.Clone();
endPose.position = rootPose.position + rootPose.forward * treeBranch.height + random.InSphere( noise );;
var rootPoint = spline.CreateChild<SplinePoint>();
var endPoint = spline.CreateChild<SplinePoint>();
rootPose.Set( rootPoint );
endPose.Set( endPoint );
var checkForOverlaps = true;
var maxChecks = 30;
var checks = 0;
while ( avoidClusters && checkForOverlaps && checks < maxChecks )
{
var line = new Line3( rootPoint.GlobalPosition, endPoint.GlobalPosition );
var testBox = line.GetBox( treeBranch.radius );
var overlapping = ocTree.GetInsideBox( testBox );
overlapping = overlapping.Filter( o => o != parentBranch );
var conflictElement = overlapping.Find(
( tb )=>
{
var tbLine = tb.GetLine3();
if ( line.DistanceToLine( tbLine ) < treeBranch.radius * minClusterDistance )
{
return true;
}
return false;
}
);
if ( conflictElement == null )
{
checkForOverlaps = false;
}
else
{
treeBranch.branchPosition = random.SelectCurveWeights( branchPosition, 100 );
rootPose = parentCurve.PoseAt( treeBranch.branchPosition );
rootPose.ApplyTwist( random.Range( 0, 360 ) );
rootRotationCenter = rotationPerLevel.Sample( normalizedLevel );
rootRotationRange = rotationVariation == null ? 0 : random.Sample( rotationVariation );
rootPose.RotateAround( Math3D.RotateXDegrees( random.Range( rootRotationCenter - rootRotationRange, rootRotationCenter + rootRotationRange ) ), rootPose.position );
endPose = rootPose.Clone();
endPose.position = rootPose.position + rootPose.forward * treeBranch.height + random.InSphere( noise );
rootPose.Set( rootPoint );
endPose.Set( endPoint );
}
checks ++;
}
if ( checks == maxChecks )
{
maxChecksFull ++;
}
}
spline.AutoOrientate();
spline.SetEditorPointSize( 0.1f / 3f );
var curve = spline.GetCurve();
treeBranch.start = curve.PositionAt( 0 );
treeBranch.end = curve.PositionAt( 1 );
var box = spline.GetBounds();
ocTree.BoxInsert( treeBranch, box );
_branches.Add( treeBranch );
return treeBranch;
}
}
}

View File

@ -0,0 +1 @@
uid://dpy0wfloqjafd

View File

@ -340,7 +340,7 @@ namespace Rokojori
public List<SubViewport> GetAllViewports() public List<SubViewport> GetAllViewports()
{ {
return Nodes.AllIn<SubViewport>( X_views, null, false ); return Nodes.GetAll<SubViewport>( X_views, null, false );
} }
public void CleanUp() public void CleanUp()

View File

@ -157,7 +157,7 @@ namespace Rokojori
{ {
if ( sourceViewportsContainer != null ) if ( sourceViewportsContainer != null )
{ {
sourceViewports = Nodes.AllIn<SubViewport>( sourceViewportsContainer, null, false ).ToArray(); sourceViewports = Nodes.GetAll<SubViewport>( sourceViewportsContainer, null, false ).ToArray();
} }
var vpTextures = Arrays.Map( sourceViewports, var vpTextures = Arrays.Map( sourceViewports,

View File

@ -531,6 +531,61 @@ namespace Rokojori
return u * ( segments + 1 ) + v; return u * ( segments + 1 ) + v;
} }
public static MeshGeometry CreateCapUVFunction( Func<Vector2,Pose> uvFunc, int uSegments, bool start = false )
{
var positions = new List<Vector3>();
var uvs = new List<Vector2>();
var normals = new List<Vector3>();
var v = start ? 0f : 1f;
for ( int i = 0; i <= uSegments; i++ )
{
var u = i / (float)( uSegments );
var lookUpUV = new Vector2( u, v );
var p = uvFunc( lookUpUV );
positions.Add( p.position );
normals.Add( ( start ? -1f : 1f ) * p.forward );
var angle = u * Mathf.Pi * 2.0f;
var uvCoord = Math2D.Circle( angle ) * 0.5f + Vector2.One * 0.5f;
uvs.Add( uvCoord );
}
var mg = new MeshGeometry();
var centerPosition = Math3D.ComputeAverage( positions );
var centerNormal = Math3D.ComputeAverage( normals ).Normalized();
var centerUV = new Vector2( 0.5f, 0.5f );
for ( int i = 0; i < positions.Count; i++ )
{
var j = ( i + 1 ) % ( positions.Count );
if ( start )
{
mg.AddTriangle(
positions[ j ], positions[i ], centerPosition,
normals[ j ], normals[ i ], centerNormal,
uvs[ j], uvs[ i ], centerUV
);
}
else
{
mg.AddTriangle(
positions[ i ], positions[ j ], centerPosition,
normals[ i ], normals[ j ], centerNormal,
uvs[ i ], uvs[ j ], centerUV
);
}
}
return mg;
}
public static MeshGeometry CreateFromUVFunction( Func<Vector2,Pose> uv, int uSegments, int vSegments, bool fullUVQuads = false ) public static MeshGeometry CreateFromUVFunction( Func<Vector2,Pose> uv, int uSegments, int vSegments, bool fullUVQuads = false )
{ {
@ -1179,6 +1234,11 @@ namespace Rokojori
} }
public void AddQuad( Quaternion rotation, float size, Vector2 uv00, Vector2 uv11 ) public void AddQuad( Quaternion rotation, float size, Vector2 uv00, Vector2 uv11 )
{
AddQuad( rotation, size, uv00, uv11, Vector3.Zero );
}
public void AddQuad( Quaternion rotation, float size, Vector2 uv00, Vector2 uv11, Vector3 offset )
{ {
var l = size * 0.5f; var l = size * 0.5f;
@ -1198,7 +1258,7 @@ namespace Rokojori
var uv01 = new Vector2( uv00.X, uv11.Y ); var uv01 = new Vector2( uv00.X, uv11.Y );
AddQuad( AddQuad(
points[ 0 ], points[ 1 ], points[ 2 ], points[ 3 ], points[ 0 ] + offset, points[ 1 ] + offset, points[ 2 ] + offset, points[ 3 ] + offset,
normal, normal, normal, normal, normal, normal, normal, normal,
uv10, uv00, uv01, uv11 uv10, uv00, uv01, uv11
); );
@ -1385,7 +1445,35 @@ namespace Rokojori
} }
public void SmoothNormals()
{
for ( int i = 0; i < normals.Count; i +=3 )
{
normals[ i ] = Vector3.Zero;
}
for ( int i = 0; i < indices.Count; i +=3 )
{
int i0 = indices[ i ];
int i1 = indices[ i + 1 ];
int i2 = indices[ i + 2 ];
var v0 = vertices[ i0 ];
var v1 = vertices[ i1 ];
var v2 = vertices[ i2 ];
var faceNormal = ( v1 - v0 ).Cross( v2 - v0 ).Normalized();
normals[ i0 ] += faceNormal;
normals[ i1 ] += faceNormal;
normals[ i2 ] += faceNormal;
}
for (int i = 0; i < normals.Count; i++ )
{
normals[ i ] = normals[ i ].Normalized();
}
}
public static ArrayMesh GenerateLODMesh( List<MeshGeometry> public static ArrayMesh GenerateLODMesh( List<MeshGeometry>
mgs, Mesh.PrimitiveType type = Mesh.PrimitiveType.Triangles, ArrayMesh arrayMesh = null, mgs, Mesh.PrimitiveType type = Mesh.PrimitiveType.Triangles, ArrayMesh arrayMesh = null,
bool generateTangents = true, LODLerpingData lerpingData = null ) bool generateTangents = true, LODLerpingData lerpingData = null )
@ -1408,6 +1496,8 @@ namespace Rokojori
return GenerateLODMesh( mgs, Mesh.PrimitiveType.Triangles, null, generateTangents, lerpingData ); return GenerateLODMesh( mgs, Mesh.PrimitiveType.Triangles, null, generateTangents, lerpingData );
} }
public ArrayMesh GenerateMesh( public ArrayMesh GenerateMesh(
Mesh.PrimitiveType type = Mesh.PrimitiveType.Triangles, ArrayMesh arrayMesh = null, Mesh.PrimitiveType type = Mesh.PrimitiveType.Triangles, ArrayMesh arrayMesh = null,
bool generateTangents = true, List<MeshGeometry> lods = null, LODLerpingData lerpingData = null ) bool generateTangents = true, List<MeshGeometry> lods = null, LODLerpingData lerpingData = null )

View File

@ -64,7 +64,7 @@ namespace Rokojori
var list = new List<QuadBillboardData>(); var list = new List<QuadBillboardData>();
this.OnAllDirectChildren<QuadBillboardDataProcessor>( this.ForEachDirectChild<QuadBillboardDataProcessor>(
( p )=> ( p )=>
{ {
list = p.Process( list ); list = p.Process( list );

View File

@ -43,6 +43,12 @@ namespace Rokojori
[Export] [Export]
public int xzPathBakeResolution = 50; public int xzPathBakeResolution = 50;
public void SetEditorPointSize( float size )
{
this.ForEachDirectChild<SplinePoint>( p => p.editorSplinePointSize = size );
}
SplineCurve splineCurve; SplineCurve splineCurve;
Vector3 min; Vector3 min;
Vector3 max; Vector3 max;
@ -228,7 +234,7 @@ namespace Rokojori
} }
#endif #endif
void AutoOrientate() public void AutoOrientate()
{ {
var list = Nodes.GetDirectChildren<SplinePoint>( this ); var list = Nodes.GetDirectChildren<SplinePoint>( this );

View File

@ -31,13 +31,18 @@ namespace Rokojori
#if TOOLS #if TOOLS
[ExportGroup("Editor")]
[Export]
public float editorSplinePointSize = 0.1f;
public void DrawGizmo( EditorNode3DGizmoPlugin gizmoPlugin, EditorNode3DGizmo gizmo ) public void DrawGizmo( EditorNode3DGizmoPlugin gizmoPlugin, EditorNode3DGizmo gizmo )
{ {
gizmo.Clear(); gizmo.Clear();
var mesh = new SphereMesh(); var mesh = new SphereMesh();
var size = 0.1f; var size = editorSplinePointSize;
mesh.Radius = size; mesh.Radius = size;
mesh.Height = size * 2f; mesh.Height = size * 2f;

View File

@ -30,42 +30,45 @@ namespace Rokojori
[Export] [Export]
public Spline spline; public Spline spline;
[Export] // [Export]
public TubeSegmentMode segmentMode = TubeSegmentMode.Fixed_Division; // public TubeSegmentMode segmentMode = TubeSegmentMode.Fixed_Division;
[Export] // [Export]
public bool useFullUVQuads = false; // public bool useFullUVQuads = false;
[Export] // [Export]
public int fixedSplineSegmentDivisions = 20; // public int fixedSplineSegmentDivisions = 20;
[Export] // [Export]
public float splineSegmentLength = 2; // public float splineSegmentLength = 2;
[Export] // [Export]
public bool undistortSplineSegments = true; // public bool undistortSplineSegments = true;
// [Export]
// public Curve twistCurve;
// [Export]
// public float radius;
// [Export]
// public int radialSegments = 8;
// [Export]
// public Curve radiusSizeCurve;
// [Export]
// public Curve radiusWidthCurve;
// [Export]
// public Curve radiusHeightCurve;
// [Export]
// public bool scaleRadiusByPathTransforms = true;
[Export] [Export]
public Curve twistCurve; public TubeGeometrySettings settings;
[Export]
public float radius;
[Export]
public int radialSegments = 8;
[Export]
public Curve radiusSizeCurve;
[Export]
public Curve radiusWidthCurve;
[Export]
public Curve radiusHeightCurve;
[Export]
public bool scaleRadiusByPathTransforms = true;
[Export] [Export]
public TubeShape[] shapes = new TubeShape[ 0 ]; public TubeShape[] shapes = new TubeShape[ 0 ];
@ -140,6 +143,12 @@ namespace Rokojori
var mg = CreateMesh(); var mg = CreateMesh();
if ( output == null )
{
output = this.CreateChild<MeshInstance3D>();
}
output.Mesh = mg.GenerateMesh(); output.Mesh = mg.GenerateMesh();
UpdateGizmos(); UpdateGizmos();
@ -151,6 +160,16 @@ namespace Rokojori
MeshGeometry CreateMesh() MeshGeometry CreateMesh()
{ {
var curve = spline.GetCurve(); var curve = spline.GetCurve();
var tubeGeometry = new TubeGeometry();
tubeGeometry.curve = curve;
tubeGeometry.settings = settings;
tubeGeometry.shapes = shapes;
return tubeGeometry.CreateMesh();
/*
int splineSegments = fixedSplineSegmentDivisions; int splineSegments = fixedSplineSegmentDivisions;
if ( TubeSegmentMode.Fixed_Division != segmentMode ) if ( TubeSegmentMode.Fixed_Division != segmentMode )
@ -259,6 +278,7 @@ namespace Rokojori
); );
return mg; return mg;
*/
} }
} }

View File

@ -0,0 +1,146 @@
using Godot;
using Rokojori;
using System.Collections.Generic;
namespace Rokojori
{
public class TubeGeometry
{
public SplineCurve curve;
public TubeShape[] shapes = [];
public TubeGeometrySettings settings;
public MeshGeometry CreateMesh()
{
int splineSegments = settings.fixedSplineSegmentDivisions;
if ( TubeSegmentMode.Fixed_Division != settings.segmentMode )
{
splineSegments = Mathf.CeilToInt( curve.ComputeLength( curve.points.Count * 3 ) / settings.splineSegmentLength );
if ( TubeSegmentMode.Maximum_Of_Both == settings.segmentMode )
{
splineSegments = Mathf.Max( splineSegments, settings.fixedSplineSegmentDivisions );
}
if ( TubeSegmentMode.Minimum_Of_Both == settings.segmentMode )
{
splineSegments = Mathf.Min( splineSegments, settings.fixedSplineSegmentDivisions );
}
}
var shapesList = new List<TubeShape>();
if ( shapes != null && shapes.Length > 0 )
{
shapesList.AddRange( shapes );
Lists.Sort( shapesList, s => s.tubePosition );
shapesList.ForEach( s => s.ClearCache() );
}
System.Func<Vector2,Pose> uvFunction = ( Vector2 uv ) =>
{
var t = settings.undistortSplineSegments ? curve.ComputeTforNormalizedCurveLength( uv.Y, splineSegments ) : uv.Y;
var index = curve.NormalizedToPointIndex( t );
var pose = curve.PoseAt( t );
// RJLog.Log( "Pose at t:", t, "rot:", pose.rotation, "pos", pose.position );
var radiusSize = settings.radius * ( settings.radiusSizeCurve == null ? 1 : settings.radiusSizeCurve.Sample( t ) );
var sizeX = radiusSize;
var sizeY = radiusSize;
var twistOffset = 0f;
if ( settings.radiusWidthCurve != null )
{
sizeX *= settings.radiusWidthCurve.Sample( t );
}
if ( settings.radiusHeightCurve != null )
{
sizeY *= settings.radiusHeightCurve.Sample( t );
}
if ( settings.scaleRadiusByPathTransforms )
{
var splineScale = curve.SmoothStepScaleByPointIndex( index );
if ( settings.scaleRadiusByPathTransforms )
{
sizeX *= splineScale.X;
sizeY *= splineScale.Y;
}
}
if ( settings.twistCurve != null )
{
twistOffset = Mathf.DegToRad( settings.twistCurve.Sample( t ) );
}
twistOffset += Mathf.DegToRad( curve.SmoothStepTwistByPointIndex( index ) * 360 );
var twistRotation = Math3D.RotateZ( twistOffset ).Normalized();
var scale = new Vector3( sizeX, sizeY, 0 );
if ( shapesList.Count > 0 )
{
if ( shapesList.Count == 1 )
{
return shapesList[ 0 ].GetPose( settings.undistortSplineSegments, splineSegments, settings.radialSegments, pose, uv, scale, twistRotation );
}
var lerpResult = Lists.LerpIndex( shapesList, uv.Y, s => s.tubePosition );
var closestShape = shapesList[ lerpResult.closestIndex ];
var secondShape = shapesList[ lerpResult.secondIndex ];
var closestPose = closestShape.GetPose( settings.undistortSplineSegments, splineSegments, settings.radialSegments, pose, uv, scale, twistRotation );
var secondPose = secondShape.GetPose( settings.undistortSplineSegments, splineSegments, settings.radialSegments, pose, uv, scale, twistRotation );
var smoothLerp = Mathf.SmoothStep( 0f, 1f, lerpResult.lerpAmount );
return Pose.Lerp( closestPose, secondPose, smoothLerp );
}
else
{
var angle = uv.X * 2f * Mathf.Pi * ( sizeX / sizeY );
var circlePose = Pose.Create( Vector3.Zero, pose.rotation * Math3D.RotateZ( angle ) );
circlePose.rotation = circlePose.rotation.Normalized();
var circleStart = Vector3.Up * radiusSize * scale;
var positionOnCircle = circlePose.Apply( circleStart );
return Pose.Create( positionOnCircle + pose.position, circlePose.rotation );
}
};
var mg = MeshGeometry.CreateFromUVFunction( uvFunction,
settings.radialSegments, splineSegments, settings.useFullUVQuads
);
if ( TubeGeometrySettings.CapType.Flat == settings.startCapType )
{
var startCapMG = MeshGeometry.CreateCapUVFunction( uvFunction, settings.radialSegments, true );
mg.Add( startCapMG );
}
if ( TubeGeometrySettings.CapType.Flat == settings.endCapType )
{
var endCapMG = MeshGeometry.CreateCapUVFunction( uvFunction, settings.radialSegments, false );
mg.Add( endCapMG );
}
return mg;
}
}
}

View File

@ -0,0 +1 @@
uid://ccc2vjq7kmqiu

View File

@ -0,0 +1,63 @@
using Godot;
using Rokojori;
using System.Collections.Generic;
namespace Rokojori
{
[Tool]
[GlobalClass]
public partial class TubeGeometrySettings:Resource
{
[Export]
public TubeSegmentMode segmentMode = TubeSegmentMode.Fixed_Division;
[Export]
public bool useFullUVQuads = false;
[Export]
public int fixedSplineSegmentDivisions = 20;
[Export]
public float splineSegmentLength = 2;
[Export]
public bool undistortSplineSegments = true;
[Export]
public Curve twistCurve;
[Export]
public float radius;
[Export]
public int radialSegments = 8;
[Export]
public Curve radiusSizeCurve;
[Export]
public Curve radiusWidthCurve;
[Export]
public Curve radiusHeightCurve;
[Export]
public bool scaleRadiusByPathTransforms = true;
public enum CapType
{
None,
Flat,
Half_Sphere
}
[Export]
public CapType startCapType;
[Export]
public Vector2 startCapUVMin =Vector2.Zero;
[Export]
public Vector2 startCapUVMax =Vector2.One;
[Export]
public CapType endCapType;
[Export]
public Vector2 endCapUVMin =Vector2.Zero;
[Export]
public Vector2 endCapUVMax =Vector2.One;
}
}

View File

@ -0,0 +1 @@
uid://c7ud5wnn6t4iw

View File

@ -31,8 +31,13 @@ namespace Rokojori
return this.Next()*( b - a ) + a; return this.Next()*( b - a ) + a;
} }
public float Sample( Curve curve ) public float Sample( Curve curve, float min = 0, float max = 0 )
{ {
if ( curve == null )
{
return Range( min, max );
}
return curve.Sample( Next() ); return curve.Sample( Next() );
} }
@ -365,6 +370,11 @@ namespace Rokojori
return _FindElementIndexWithWeights( weights, Next() * sumWeights ); return _FindElementIndexWithWeights( weights, Next() * sumWeights );
} }
public float SelectCurveWeights( Curve curve, int numSteps = 100 )
{
return IndexFromCurveWeights( curve, numSteps ) / (float) numSteps;
}
public int IndexFromCurveWeights( Curve curve, int numIndices ) public int IndexFromCurveWeights( Curve curve, int numIndices )
{ {
var weights = new List<float>(); var weights = new List<float>();

View File

@ -515,6 +515,8 @@ namespace Rokojori.Reallusion
mat.opacity.AssignFor( m, opacity.GetOr( 1 ) * opacityScale ); mat.opacity.AssignFor( m, opacity.GetOr( 1 ) * opacityScale );
mat.blendAmount.AssignFor( m, 0f ); mat.blendAmount.AssignFor( m, 0f );
mat.roughnessOffset.AssignFor( m, settings.configuration.roughnessOffset );
mat.metallicOffset.AssignFor( m, settings.configuration.metallicOffset );
textures.value.ForEach( textures.value.ForEach(
( t ) => ( t ) =>

View File

@ -14,8 +14,7 @@ namespace Rokojori.Reallusion
public partial class CCImportConfiguration:Resource public partial class CCImportConfiguration:Resource
{ {
[Export]
public bool setRenderPrioritiesForHair = true;
[Export] [Export]
public float gammaForAlbedoColor = 1.0f / 2.2f; public float gammaForAlbedoColor = 1.0f / 2.2f;
@ -49,12 +48,26 @@ namespace Rokojori.Reallusion
[Export] [Export]
public HairShaderMode hairShaderMode = HairShaderMode.Scissor_AlphaBack_AlphaFront; public HairShaderMode hairShaderMode = HairShaderMode.Scissor_AlphaBack_AlphaFront;
[ExportGroup( "Hair/Render Order")]
[Export]
public bool setRenderPrioritiesForHair = true;
[Export]
public CCMaterialTarget[] preRenderPriorityTargets = [];
[ExportGroup( "Hair")]
[Export(PropertyHint.Range,"0,1")] [Export(PropertyHint.Range,"0,1")]
public float naturalColors = 0.5f; public float naturalColors = 0.5f;
[Export(PropertyHint.Range,"0,1")] [Export(PropertyHint.Range,"0,1")]
public float maximumHighlightAmount = 0.5f; public float maximumHighlightAmount = 0.5f;
[Export(PropertyHint.Range,"-1,1")]
public float roughnessOffset = -0.1f;
[Export(PropertyHint.Range,"-1,1")]
public float metallicOffset = 0.0f;
[Export] [Export]
public float hairOpacityGamma = 1.2f; public float hairOpacityGamma = 1.2f;

View File

@ -26,6 +26,13 @@ namespace Rokojori.Reallusion
[Export] [Export]
public CCMaterialType materialType; public CCMaterialType materialType;
[Export]
public int materialRenderPriority = 0;
[Export]
public bool assignRenderPriority = false;
[Export] [Export]
public int materialRenderPriorityIndexBack; public int materialRenderPriorityIndexBack;

View File

@ -130,6 +130,31 @@ namespace Rokojori.Reallusion
{ {
var renderIndex = 0; var renderIndex = 0;
for ( int i = 0; i < importSettings.configuration.preRenderPriorityTargets.Length; i++ )
{
var target = importSettings.configuration.preRenderPriorityTargets[ i ];
var targetName = target.meshName;
var targetIndex = target.materialSurfaceIndex;
var mesh = importSettings.meshes.Find( m => m.meshName == targetName );
if ( mesh == null || targetIndex < 0 || targetIndex > mesh.materials.Length )
{
this.LogInfo( "Mesh not found:",
targetName, ":", targetIndex
);
continue;
}
var material = mesh.materials[ targetIndex ];
material.assignRenderPriority = true;
material.materialRenderPriority = renderIndex;
renderIndex++;
}
for ( int i = 0; i < importSettings.meshes.Length; i++ ) for ( int i = 0; i < importSettings.meshes.Length; i++ )
{ {
var ms = importSettings.meshes[ i ]; var ms = importSettings.meshes[ i ];
@ -180,6 +205,11 @@ namespace Rokojori.Reallusion
materialGenerator.CreateMaterial( m.materials[ i ], settings ); materialGenerator.CreateMaterial( m.materials[ i ], settings );
if ( settings.assignRenderPriority )
{
material.RenderPriority = settings.materialRenderPriority;
}
var overwriteMaterial = settings.configuration.GetOverwriteMaterial( m.name, i ); var overwriteMaterial = settings.configuration.GetOverwriteMaterial( m.name, i );
if ( overwriteMaterial != null ) if ( overwriteMaterial != null )

View File

@ -11,6 +11,7 @@ uniform sampler2D texture_albedo : source_color, filter_linear_mipmap_anisotropi
uniform vec3 hslOffset = vec3( 0, 0, 0 ); uniform vec3 hslOffset = vec3( 0, 0, 0 );
uniform sampler2D texture_blend : source_color,hint_default_white, filter_linear_mipmap_anisotropic; uniform sampler2D texture_blend : source_color,hint_default_white, filter_linear_mipmap_anisotropic;
uniform float blendAmount:hint_range(0, 1) =1; uniform float blendAmount:hint_range(0, 1) =1;
// uniform float grow:hint_range(-0.01, 0.01, 0.0001) = 00;
uniform float naturalColors: hint_range(0, 1) = 0.5; uniform float naturalColors: hint_range(0, 1) = 0.5;
@ -100,7 +101,7 @@ group_uniforms;
group_uniforms Roughness_Metallness; group_uniforms Roughness_Metallness;
uniform float roughness : hint_range(0.0, 1.0) = 1.0; uniform float roughness : hint_range(0.0, 1.0) = 1.0;
uniform float roughnessOffset: hint_range(-1.0, 1.0) = -0.1; uniform float roughnessOffset: hint_range(-1.0, 1.0) = 0;
uniform sampler2D texture_roughness : hint_roughness_r, filter_linear_mipmap_anisotropic, repeat_enable; uniform sampler2D texture_roughness : hint_roughness_r, filter_linear_mipmap_anisotropic, repeat_enable;
uniform float metallic : hint_range(0.0, 1.0, 0.01) = 1.0; uniform float metallic : hint_range(0.0, 1.0, 0.01) = 1.0;
@ -211,6 +212,8 @@ void vertex()
cameraDistance, highlightFadeOutStart, highlightFadeOutEnd, highlightFadeOutPower, highlightFadeOutAmount cameraDistance, highlightFadeOutStart, highlightFadeOutEnd, highlightFadeOutPower, highlightFadeOutAmount
) * maximumHighlightAmount; ) * maximumHighlightAmount;
// VERTEX += NORMAL * grow;
#ifdef USE_SHADOW #ifdef USE_SHADOW
float farAmount = computeCameraFadeout( float farAmount = computeCameraFadeout(

View File

@ -1,5 +1,5 @@
shader_type spatial; shader_type spatial;
render_mode blend_mix, depth_draw_always, cull_disabled, diffuse_burley, specular_schlick_ggx, depth_prepass_alpha; render_mode blend_mix, depth_draw_opaque, cull_disabled, diffuse_burley, specular_schlick_ggx, depth_prepass_alpha;
#define USE_ALPHA_DISCARD //#define USE_ALPHA_DISCARD
#include "res://addons/rokojori_action_library/Runtime/Reallusion/Shaders/Hair/CCHairBase.gdshaderinc" #include "res://addons/rokojori_action_library/Runtime/Reallusion/Shaders/Hair/CCHairBase.gdshaderinc"

View File

@ -26,8 +26,6 @@ void main()
vec4 currentPixel = ensureValidVec4( imageLoad( currentImage, currentPosition ) ); vec4 currentPixel = ensureValidVec4( imageLoad( currentImage, currentPosition ) );
vec4 lastPixel = ensureValidVec4( imageLoad( lastProcessedImage, currentPosition ) ); vec4 lastPixel = ensureValidVec4( imageLoad( lastProcessedImage, currentPosition ) );
vec4 nextPixel = currentPixel + params.smearing * ( lastPixel - currentPixel ); vec4 nextPixel = currentPixel + params.smearing * ( lastPixel - currentPixel );
vec4 combinedPixel = mix( currentPixel, nextPixel, params.amount ); vec4 combinedPixel = mix( currentPixel, nextPixel, params.amount );

View File

@ -3,10 +3,51 @@
#include "res://addons/rokojori_action_library/Runtime/Shading/Library/Math.gdshaderinc" #include "res://addons/rokojori_action_library/Runtime/Shading/Library/Math.gdshaderinc"
#include "res://addons/rokojori_action_library/Runtime/Shading/Library/Transform.gdshaderinc" #include "res://addons/rokojori_action_library/Runtime/Shading/Library/Transform.gdshaderinc"
/*
uniform bool windEnabled = false;
uniform float windOcclusionAmount = 0;
uniform float windStrength = 0;
uniform vec2 windSpeed = vec2(1,1);
uniform float windScale = 0.1;
uniform sampler2D windNoise;
uniform vec2 windNoiseAngleOffset;
uniform vec2 windNoiseStrengthOffset;
uniform float windStart = 0;
uniform float windEnd = 1;
uniform float windWeightCurve:hint_range(0,1) = 0.5f;
uniform float windHeightCompensation :hint_range(0,1) = 0.5f;
uniform float windNormalBending :hint_range(0,1) = 0.1f;
varying float vertexWindAO;
applyWind(
TIME,
MODEL_MATRIX,
VERTEX,
NORMAL,
windAO,
windOcclusionAmount,
windStrength,
windSpeed,
windScale,
windNoise,
windNoiseAngleOffset,
windNoiseStrengthOffset,
windStart,
windEnd,
windWeightCurve,
windHeightCompensation,
windNormalBending
);
*/
void applyWind( void applyWind(
float _TIME, float _TIME,
mat4 _MODEL_MATRIX, mat4 _MODEL_MATRIX,
inout vec3 _VERTEX, inout vec3 _VERTEX,
inout vec3 _NORMAL,
inout float _windAO,
float _windOcclusionAmount,
float _windStrength, float _windStrength,
vec2 _windSpeed, vec2 _windSpeed,
float _windScale, float _windScale,
@ -16,18 +57,23 @@ void applyWind(
float _windStart, float _windStart,
float _windEnd, float _windEnd,
float _windWeightCurve, float _windWeightCurve,
float _windHeightCompensation float _windHeightCompensation,
float _windNormalBending
) )
{ {
float _windAmount = normalizeToRange01( _VERTEX.y, _windStart, _windEnd ); float _windAmount = normalizeToRange01( _VERTEX.y, _windStart, _windEnd );
float rawWindAmount = _windAmount; float rawWindAmount = _windAmount;
float originalHeight = _VERTEX.y;
_windAmount = mix( _windAmount, _windAmount * _windAmount, _windWeightCurve ); _windAmount = mix( _windAmount, _windAmount * _windAmount, _windWeightCurve );
vec3 worldVertex = localToWorld( _VERTEX, _MODEL_MATRIX ); vec3 worldVertex = localToWorld( _VERTEX, _MODEL_MATRIX );
vec2 _windUV = _TIME * _windSpeed + worldVertex.xz * _windScale; vec2 _windUV = _TIME * _windSpeed + worldVertex.xz * _windScale;
float angle = texture( _windNoise, _windUV + _windNoiseAngleOffset).r * PI * 2.0; float angle = texture( _windNoise, _windUV + _windNoiseAngleOffset).r * PI * 2.0;
float strength = texture( _windNoise, _windUV + _windNoiseStrengthOffset ).r * _windStrength; float generalStrength = texture( _windNoise, _windUV + _windNoiseStrengthOffset ).r;
float strength = generalStrength * _windStrength;
vec2 circle = onCircle( angle ) * strength; vec2 circle = onCircle( angle ) * strength;
_VERTEX = worldToLocal( worldVertex + vec3( circle.x, 0, circle.y ) * _windAmount, _MODEL_MATRIX ); _VERTEX = worldToLocal( worldVertex + vec3( circle.x, 0, circle.y ) * _windAmount, _MODEL_MATRIX );
float minY = min( _VERTEX.y, 0 ); float minY = min( _VERTEX.y, 0 );
_VERTEX.y = mix( _VERTEX.y, max( minY, _VERTEX.y - strength * _windAmount), _windHeightCompensation * 4.0f ); _VERTEX.y = mix( _VERTEX.y, max( minY, _VERTEX.y - strength * _windAmount), _windHeightCompensation * 4.0f );
_NORMAL = normalize( mix( _NORMAL, vec3( circle.x, -1, circle.y ), generalStrength * _windNormalBending ) );
_windAO = mix( 1, 0, generalStrength * _windOcclusionAmount);
} }

View File

@ -10,8 +10,99 @@ using System.Linq;
namespace Rokojori namespace Rokojori
{ {
public class MatchRange
{
public int start;
public int length;
public MatchRange( int s, int l )
{
start = s;
length = l;
}
public string GetString( string source)
{
return source.Substring( start, length );
}
}
public class MatchResult
{
public string source;
public bool matched = false;
public MatchRange match;
public List<MatchRange> groups;
public string this[ int index ]
{
get
{
if ( ! matched )
{
return null;
}
if ( index == 0 )
{
return match.GetString( source );
}
if ( groups != null && index > 0 && index <= groups.Count )
{
return groups[ index - 1 ] == null ? null : groups[ index - 1 ].GetString( source );
}
return null;
}
}
}
public static class RegexUtility public static class RegexUtility
{ {
public static MatchResult Exec( this string source, string pattern, RegexOptions options = RegexOptions.None )
{
var regex = new Regex( pattern );
var match = regex.Match( source );
var result = new MatchResult();
result.source = source;
result.matched = match.Success;
if ( match.Success )
{
result.match = new MatchRange( match.Index, match.Length );
result.groups = new List<MatchRange>();
for ( int i = 1; i < match.Groups.Count; i++ )
{
var group = match.Groups[i];
if ( group.Success )
{
result.groups.Add( new MatchRange( group.Index, group.Length ) );
}
else
{
result.groups.Add( null );
}
}
}
return null;
}
public static bool Matches( this string source, string regexPattern, RegexOptions options = RegexOptions.None )
{
var match = new Regex( regexPattern, options ).Match( source );
return match != null && match.Success;
}
public static Regex MakeSticky( Regex regex ) public static Regex MakeSticky( Regex regex )
{ {
var source = MakeSourceSticky( regex + "" ); var source = MakeSourceSticky( regex + "" );