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

View File

@ -9,6 +9,12 @@ namespace Rokojori
{
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
{
return GetAnyChild<T>( node );
@ -161,7 +167,7 @@ namespace Rokojori
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>();
@ -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 )
{
@ -506,7 +524,7 @@ namespace Rokojori
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 )
{
@ -537,10 +555,6 @@ namespace Rokojori
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
{
@ -566,6 +580,23 @@ namespace Rokojori
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
{
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 )
{

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

@ -177,7 +177,122 @@ namespace Rokojori
var num = NumChildren( node );
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 )
{

View File

@ -26,14 +26,21 @@ namespace Rokojori
[Export]
public Node3D graphics;
[Export]
public Smoothing rotationSmoothing;
[Export]
public Smoothing positionSmoothing;
[Export]
public Node3D groundedTransform;
[Export]
public float lastGroundedHeight = 0;
public float delta = 0;
@ -54,6 +61,17 @@ namespace Rokojori
graphics.GlobalPosition = Smoothing.Apply( positionSmoothing, body.GlobalPosition, 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 );
}

View File

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

View File

@ -31,6 +31,17 @@ namespace Rokojori
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 )
@ -74,6 +85,40 @@ namespace Rokojori
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 )
{
var rootCell = GetRootCell( _getPosition( data ) );
@ -91,6 +136,26 @@ namespace Rokojori
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 )
{
var level = 0;

View File

@ -49,6 +49,27 @@ namespace Rokojori
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 )
{
var cell = new OcTreeCell<T>();
@ -83,6 +104,32 @@ namespace Rokojori
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 )
{
if ( isLeaf )

View File

@ -100,6 +100,39 @@ namespace Rokojori
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 )
{
if ( ! Range.Contains( min.X, max.X, point.X ) )

View File

@ -38,6 +38,13 @@ namespace Rokojori
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;
}
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 )
{
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 )
{
rotation = rotation * Math3D.RotateZ( twist );

View File

@ -264,6 +264,7 @@ namespace Rokojori
return center / points.Length;
}
public static Vector3 Center<N>( List<N> points ) where N:Node3D
{
var center = Vector3.Zero;
@ -600,6 +601,11 @@ namespace Rokojori
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 )
{
if ( radians == 0 ) { return Quaternion.Identity; }
@ -938,6 +944,18 @@ namespace Rokojori
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 )
{
return GetWorldBoundsFrom( node, onlyVisible );

View File

@ -10,6 +10,9 @@ namespace Rokojori
{
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>();
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)
{
@ -426,6 +428,30 @@ namespace Rokojori
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 )
{
var curve = new Curve();

View File

@ -39,7 +39,7 @@ namespace Rokojori
return true;
}
if ( Contains( min ) || Contains( max ) )
if ( Contains( other.min ) || Contains( other.max ) )
{
return true;
}
@ -95,6 +95,25 @@ namespace Rokojori
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()
{
return Nodes.AllIn<SubViewport>( X_views, null, false );
return Nodes.GetAll<SubViewport>( X_views, null, false );
}
public void CleanUp()

View File

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

View File

@ -531,6 +531,61 @@ namespace Rokojori
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 )
{
@ -1176,9 +1231,14 @@ namespace Rokojori
mg.ApplyUVTransform( uvTransform );
Add( mg );
}
}
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;
@ -1198,7 +1258,7 @@ namespace Rokojori
var uv01 = new Vector2( uv00.X, uv11.Y );
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,
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>
mgs, Mesh.PrimitiveType type = Mesh.PrimitiveType.Triangles, ArrayMesh arrayMesh = null,
bool generateTangents = true, LODLerpingData lerpingData = null )
@ -1408,6 +1496,8 @@ namespace Rokojori
return GenerateLODMesh( mgs, Mesh.PrimitiveType.Triangles, null, generateTangents, lerpingData );
}
public ArrayMesh GenerateMesh(
Mesh.PrimitiveType type = Mesh.PrimitiveType.Triangles, ArrayMesh arrayMesh = null,
bool generateTangents = true, List<MeshGeometry> lods = null, LODLerpingData lerpingData = null )

View File

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

View File

@ -42,7 +42,13 @@ namespace Rokojori
[Export]
public int xzPathBakeResolution = 50;
public void SetEditorPointSize( float size )
{
this.ForEachDirectChild<SplinePoint>( p => p.editorSplinePointSize = size );
}
SplineCurve splineCurve;
Vector3 min;
Vector3 max;
@ -228,7 +234,7 @@ namespace Rokojori
}
#endif
void AutoOrientate()
public void AutoOrientate()
{
var list = Nodes.GetDirectChildren<SplinePoint>( this );
@ -253,7 +259,7 @@ namespace Rokojori
{
var first = list[ 0 ];
var firstDirection = ( first.GlobalPosition - point.GlobalPosition ).Normalized();
point.LookTowards( firstDirection, up);
point.LookTowards( firstDirection, up );
}
continue;

View File

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

View File

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

View File

@ -514,7 +514,9 @@ namespace Rokojori.Reallusion
// mat.specular.AssignFor( m, 0.5f );
mat.opacity.AssignFor( m, opacity.GetOr( 1 ) * opacityScale );
mat.blendAmount.AssignFor( m, 0f );
mat.roughnessOffset.AssignFor( m, settings.configuration.roughnessOffset );
mat.metallicOffset.AssignFor( m, settings.configuration.metallicOffset );
textures.value.ForEach(
( t ) =>

View File

@ -14,8 +14,7 @@ namespace Rokojori.Reallusion
public partial class CCImportConfiguration:Resource
{
[Export]
public bool setRenderPrioritiesForHair = true;
[Export]
public float gammaForAlbedoColor = 1.0f / 2.2f;
@ -49,12 +48,26 @@ namespace Rokojori.Reallusion
[Export]
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")]
public float naturalColors = 0.5f;
[Export(PropertyHint.Range,"0,1")]
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]
public float hairOpacityGamma = 1.2f;

View File

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

View File

@ -130,6 +130,31 @@ namespace Rokojori.Reallusion
{
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++ )
{
var ms = importSettings.meshes[ i ];
@ -180,6 +205,11 @@ namespace Rokojori.Reallusion
materialGenerator.CreateMaterial( m.materials[ i ], settings );
if ( settings.assignRenderPriority )
{
material.RenderPriority = settings.materialRenderPriority;
}
var overwriteMaterial = settings.configuration.GetOverwriteMaterial( m.name, i );
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 sampler2D texture_blend : source_color,hint_default_white, filter_linear_mipmap_anisotropic;
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;
@ -100,7 +101,7 @@ group_uniforms;
group_uniforms Roughness_Metallness;
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 float metallic : hint_range(0.0, 1.0, 0.01) = 1.0;
@ -211,6 +212,8 @@ void vertex()
cameraDistance, highlightFadeOutStart, highlightFadeOutEnd, highlightFadeOutPower, highlightFadeOutAmount
) * maximumHighlightAmount;
// VERTEX += NORMAL * grow;
#ifdef USE_SHADOW
float farAmount = computeCameraFadeout(

View File

@ -1,5 +1,5 @@
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"

View File

@ -24,9 +24,7 @@ void main()
ivec2 currentPosition = ivec2( gl_GlobalInvocationID.xy );
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 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/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(
float _TIME,
mat4 _MODEL_MATRIX,
inout vec3 _VERTEX,
inout vec3 _NORMAL,
inout float _windAO,
float _windOcclusionAmount,
float _windStrength,
vec2 _windSpeed,
float _windScale,
@ -16,18 +57,23 @@ void applyWind(
float _windStart,
float _windEnd,
float _windWeightCurve,
float _windHeightCompensation
float _windHeightCompensation,
float _windNormalBending
)
{
float _windAmount = normalizeToRange01( _VERTEX.y, _windStart, _windEnd );
float rawWindAmount = _windAmount;
float originalHeight = _VERTEX.y;
_windAmount = mix( _windAmount, _windAmount * _windAmount, _windWeightCurve );
vec3 worldVertex = localToWorld( _VERTEX, _MODEL_MATRIX );
vec2 _windUV = _TIME * _windSpeed + worldVertex.xz * _windScale;
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;
_VERTEX = worldToLocal( worldVertex + vec3( circle.x, 0, circle.y ) * _windAmount, _MODEL_MATRIX );
float minY = min( _VERTEX.y, 0 );
_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
{
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 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 )
{
var source = MakeSourceSticky( regex + "" );