737 lines
19 KiB
C#
737 lines
19 KiB
C#
![]() |
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;
|
||
|
}
|
||
|
|
||
|
|
||
|
}
|
||
|
}
|