Skip to content

Commit b134522

Browse files
committed
update the side branch logic of the tree3D.
1 parent 10ed786 commit b134522

File tree

2 files changed

+145
-48
lines changed

2 files changed

+145
-48
lines changed

BeingAliveLanguage/BALCore/coreTree3D.cs

Lines changed: 144 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ public Tree3D(Plane pln, double globalScale, double trunkScale, int seed = 0,
8282

8383
mMaxSideBranchLen = 0.5 * mScaledLen;
8484
mMinSideBranchLen = 0.25 * mScaledLen * mTScale / mStage1;
85+
86+
// The ratio between top and bottom max branch lengths (top is this fraction of bottom)
87+
mBranchLenTaperRatio = 0.6;
8588

8689
mId = id;
8790
}
@@ -102,6 +105,7 @@ public Tree3D Copy() {
102105
copy.mAngleTop = this.mAngleTop;
103106
copy.mMaxSideBranchLen = this.mMaxSideBranchLen;
104107
copy.mMinSideBranchLen = this.mMinSideBranchLen;
108+
copy.mBranchLenTaperRatio = this.mBranchLenTaperRatio;
105109
copy.mNearestTreeDist = this.mNearestTreeDist;
106110
copy.mNearestTree = new Point3d(this.mNearestTree);
107111
copy.mSoloRadius = this.mSoloRadius;
@@ -276,12 +280,22 @@ public void GrowStage1() {
276280
// Initial rotation for the first (bottom) layer
277281
curDir.Rotate(baseAngle, mPln.XAxis);
278282

279-
// Calculate branch length
280-
double branchLenIncrement = (mMaxSideBranchLen - mMinSideBranchLen) / mStage1;
281-
var bottomBranchLen = mMinSideBranchLen + (auxPhaseS1 * branchLenIncrement);
283+
// Calculate branch length for Stage 1
284+
// Stage 1 should reach ~60% of the final max length
285+
// This leaves room for gradual growth in phases 5-10
286+
double stage1MaxRatio = 0.6; // Stage 1 reaches 60% of final max
287+
double stage1MaxLen = mMaxSideBranchLen * stage1MaxRatio;
288+
double stage1MinLen = mMinSideBranchLen;
289+
290+
double branchLenIncrement = (stage1MaxLen - stage1MinLen) / mStage1;
291+
var bottomBranchLen = stage1MinLen + (auxPhaseS1 * branchLenIncrement);
282292

283293
// Track current layer for gradual angle calculation
284294
int layerCount = 0;
295+
296+
// Stage 1 taper ratio - more aggressive than final taper to create pyramid shape
297+
// Top layer branches are much shorter than bottom to leave room for trunk tip branching
298+
double stage1TaperRatio = 0.3; // Top branches are 30% of bottom (more aggressive than final 60%)
285299

286300
// Calculate branch position on the trunk
287301
for (int segIdx = 0; segIdx < auxPhaseS1; segIdx++) {
@@ -295,11 +309,13 @@ public void GrowStage1() {
295309
for (int brNum = 0; brNum < mNumBranchPerLayer; brNum++) {
296310
var node = new BranchNode3D(mAllNode.Count, segIdx + 1, pt);
297311

298-
// Calculate branch length based on position and growth
299-
double branchLen =
300-
(totalBranchLayer == 1 ? mMinSideBranchLen
301-
: Utils.remap(curBranchLayer, 1, totalBranchLayer,
302-
bottomBranchLen, mMinSideBranchLen));
312+
// Apply aggressive taper for Stage 1: create pyramid/triangle shape
313+
// Bottom branches are full length, top branches are much shorter
314+
double layerRatio = (totalBranchLayer == 1) ? 0.0
315+
: (double)curBranchLayer / (totalBranchLayer - 1);
316+
double layerTaperFactor = 1.0 - (1.0 - stage1TaperRatio) * layerRatio;
317+
318+
double branchLen = bottomBranchLen * layerTaperFactor;
303319

304320
// Rotation in XY-plane
305321
double horRotRadian = Math.PI * 2 / mNumBranchPerLayer;
@@ -348,31 +364,115 @@ public void GrowStage1() {
348364
AddNodeToTree(mBaseNode, topNode);
349365
}
350366

367+
/// <summary>
368+
/// Calculate the target branch length for a given node based on its height and current phase.
369+
/// This provides a unified length calculation across all stages for gradual growth.
370+
/// </summary>
371+
/// <param name="node">The branch node</param>
372+
/// <param name="currentPhase">The current growth phase</param>
373+
/// <returns>Target branch length for this node at this phase</returns>
374+
private double GetTargetBranchLen(BranchNode3D node, int currentPhase) {
375+
// Get the height ratio of this node (0 = bottom, 1 = top)
376+
double nodeHeight = 0;
377+
double trunkHeight = mScaledLen * mTScale;
378+
379+
if (node.mBranch.Count > 0) {
380+
mPln.RemapToPlaneSpace(node.mBranch[0].PointAtStart, out Point3d localPt);
381+
nodeHeight = localPt.Z;
382+
}
383+
double heightRatio = Math.Clamp(nodeHeight / trunkHeight, 0.0, 1.0);
384+
385+
// Taper transitions from aggressive (Stage 1) to final (Stage 3)
386+
// Stage 1 taper: 0.3 (pyramid shape)
387+
// Final taper: 0.6 (more uniform mature tree)
388+
double stage1TaperRatio = 0.3;
389+
double finalTaperRatio = mBranchLenTaperRatio; // 0.6
390+
391+
// Calculate taper factor based on phase
392+
double taperFactor;
393+
if (currentPhase <= mStage1) {
394+
// Phase 1-4: use aggressive Stage 1 taper
395+
taperFactor = 1.0 - (1.0 - stage1TaperRatio) * heightRatio;
396+
} else if (currentPhase <= mStage3) {
397+
// Phase 5-10: transition from Stage 1 taper to final taper
398+
double phasesInTransition = mStage3 - mStage1;
399+
double transitionProgress = (double)(currentPhase - mStage1) / phasesInTransition;
400+
// Interpolate taper ratio from stage1 to final
401+
double currentTaperRatio = stage1TaperRatio + (finalTaperRatio - stage1TaperRatio) * transitionProgress;
402+
taperFactor = 1.0 - (1.0 - currentTaperRatio) * heightRatio;
403+
} else {
404+
// Phase 11-12: use final taper
405+
taperFactor = 1.0 - (1.0 - finalTaperRatio) * heightRatio;
406+
}
407+
408+
// Calculate the final max length for this node (at phase 10)
409+
double finalMaxLen = mMaxSideBranchLen * (1.0 - (1.0 - finalTaperRatio) * heightRatio);
410+
411+
// Calculate growth progress based on phase
412+
// Phase 1-4: grow from minLen to 60% of finalMaxLen
413+
// Phase 5-10: grow from 60% to 100% of finalMaxLen (with front-loaded curve)
414+
// Phase 11-12: no growth (dying phase)
415+
416+
double stage1MaxRatio = 0.6; // Stage 1 reaches 60% of final max
417+
double minLen = mMinSideBranchLen * taperFactor;
418+
double stage1MaxLen = finalMaxLen * stage1MaxRatio;
419+
420+
double targetLen;
421+
422+
if (currentPhase <= mStage1) {
423+
// Phase 1-4: grow from minLen toward stage1MaxLen with Stage 1 taper
424+
double progress = (double)currentPhase / mStage1;
425+
// Apply Stage 1 taper to the target length
426+
double stage1FinalMaxLen = mMaxSideBranchLen * (1.0 - (1.0 - stage1TaperRatio) * heightRatio);
427+
double stage1Target = stage1FinalMaxLen * stage1MaxRatio;
428+
targetLen = minLen + (stage1Target - minLen) * progress;
429+
} else if (currentPhase <= mStage3) {
430+
// Phase 5-10: grow from stage1MaxLen toward finalMaxLen
431+
// Use front-loaded growth curve (square root) so branches grow faster early
432+
double phasesInMatureGrowth = mStage3 - mStage1; // 6 phases (5,6,7,8,9,10)
433+
double linearProgress = (double)(currentPhase - mStage1) / phasesInMatureGrowth;
434+
// Square root curve: faster growth in early phases, slower as it approaches max
435+
double progress = Math.Sqrt(linearProgress);
436+
437+
// Calculate what the length was at end of Stage 1
438+
double stage1FinalMaxLen = mMaxSideBranchLen * (1.0 - (1.0 - stage1TaperRatio) * heightRatio);
439+
double stage1EndLen = stage1FinalMaxLen * stage1MaxRatio;
440+
441+
targetLen = stage1EndLen + (finalMaxLen - stage1EndLen) * progress;
442+
} else {
443+
// Phase 11-12: no growth, stay at phase 10 level
444+
targetLen = finalMaxLen;
445+
}
446+
447+
return targetLen;
448+
}
449+
351450
public void GrowStage2(int dupNum = 0) {
352451
// auxiliary phase variable
353452
var auxPhaseS2 = Math.Min(mPhase, mStage2);
354453

355-
// ! phase 5-10: branching phase
454+
// ! phase 5-8: branching phase
356455
var splitInitLen = mScaledLen * 0.2;
357456

358457
// Select nodes to branch: top nodes from previous phase and selected side branches
359458
for (int curPhase = mStage1 + 1; curPhase <= auxPhaseS2; curPhase++) {
360-
// Continue to grow branch emerged in Stage 1
361-
var addedPhase = curPhase - mStage1;
362-
var lenIncrementPerPhase = (mMaxSideBranchLen - mMinSideBranchLen) / mStage1;
459+
// Continue to grow branches emerged in Stage 1
363460
foreach (var node in mTrunkBranchNode) {
364-
if (!mBranchRelation.ContainsKey(node.mID)) {
365-
var tmpLst = new List<Curve>();
366-
foreach (var br in node.mBranch) {
367-
var dir = br.PointAtEnd - br.PointAtStart;
368-
dir.Unitize();
369-
var increLen = addedPhase * lenIncrementPerPhase;
370-
var len = Math.Min(mMaxSideBranchLen, br.GetLength() + increLen);
371-
372-
tmpLst.Add(new Line(br.PointAtStart, dir * len).ToNurbsCurve());
373-
};
374-
node.mBranch = tmpLst;
375-
}
461+
var tmpLst = new List<Curve>();
462+
// Get the target length for this node at this phase
463+
double targetLen = GetTargetBranchLen(node, curPhase);
464+
465+
foreach (var br in node.mBranch) {
466+
var currentLen = br.GetLength();
467+
var dir = br.PointAtEnd - br.PointAtStart;
468+
dir.Unitize();
469+
470+
// Grow toward target, but never shrink
471+
var newLen = Math.Max(currentLen, targetLen);
472+
473+
tmpLst.Add(new Line(br.PointAtStart, dir * newLen).ToNurbsCurve());
474+
};
475+
node.mBranch = tmpLst;
376476
}
377477

378478
// for each end node, branch out several new branches
@@ -435,35 +535,36 @@ public void GrowStage3() {
435535
// auxiliary phase variable
436536
var auxPhaseS3 = Math.Min(mPhase, mStage3);
437537

438-
// ! phase 5-10: branching phase
538+
// ! phase 9-10: mature phase - side branches continue to grow
439539
var splitInitLen = mScaledLen * 0.2;
440540

441541
// Select nodes to branch: top nodes from previous phase and selected side branches
442542
for (int curPhase = mStage2 + 1; curPhase <= auxPhaseS3; curPhase++) {
443-
// Continue to grow branch emerged in Stage 1
444-
var addedPhase = curPhase - mStage1;
445-
var lenIncrementPerPhase = (mMaxSideBranchLen - mMinSideBranchLen) / mStage1;
543+
// Continue to grow ALL trunk branches during mature phases
446544
foreach (var node in mTrunkBranchNode) {
447-
if (!mBranchRelation.ContainsKey(node.mID)) {
448-
var tmpLst = new List<Curve>();
449-
foreach (var br in node.mBranch) {
450-
var dir = br.PointAtEnd - br.PointAtStart;
451-
dir.Unitize();
452-
var increLen = addedPhase * lenIncrementPerPhase;
453-
var len = Math.Min(mMaxSideBranchLen, br.GetLength() + increLen);
454-
455-
tmpLst.Add(new Line(br.PointAtStart, dir * len).ToNurbsCurve());
456-
};
457-
node.mBranch = tmpLst;
458-
}
545+
var tmpLst = new List<Curve>();
546+
// Get the target length for this node at this phase
547+
double targetLen = GetTargetBranchLen(node, curPhase);
548+
549+
foreach (var br in node.mBranch) {
550+
var currentLen = br.GetLength();
551+
var dir = br.PointAtEnd - br.PointAtStart;
552+
dir.Unitize();
553+
554+
// Grow toward target, but never shrink
555+
var newLen = Math.Max(currentLen, targetLen);
556+
557+
tmpLst.Add(new Line(br.PointAtStart, dir * newLen).ToNurbsCurve());
558+
};
559+
node.mBranch = tmpLst;
459560
}
460561

461562
// for each end node, branch out several new branches
462563
var startNodeId = mAllNode.Count;
463564
var nodesToSplit = mAllNode.Where(node => node.flagSplittable == true).ToList();
464565

465-
// Select additional side branches to grow (only the 1st phase of stage 2)
466-
if (curPhase == mStage1 + 1) {
566+
// Select additional side branches to grow (only the 1st phase of stage 3)
567+
if (curPhase == mStage2 + 1) {
467568
var sideNodeToBranch = SelectTopUnbranchedNodes(dupNum);
468569
sideNodeToBranch.ForEach(x => x.ToggleSplitable());
469570
nodesToSplit.AddRange(sideNodeToBranch);
@@ -499,11 +600,6 @@ public void GrowStage3() {
499600
newNode.flagBranchSplit.Add(true); // make sure new node labeled split
500601
newNode.ToggleSplitable();
501602
AddNodeToTree(node, newNode);
502-
503-
// store the root splitted branches
504-
if (curPhase == mStage1 + 1) {
505-
mBaseSplittedNode.Add(newNode);
506-
}
507603
}
508604

509605
// after the split, toggle it so that the next iteration will not split it again
@@ -916,6 +1012,7 @@ public void GetTrunckVolume(in int curPhase, out Mesh trunkMesh) {
9161012
public double mAngleTop;
9171013
public double mMaxSideBranchLen;
9181014
public double mMinSideBranchLen;
1015+
public double mBranchLenTaperRatio; // Ratio of top branch max length to bottom branch max length
9191016
public double mHeight;
9201017
public string mId;
9211018

BeingAliveLanguage/BALinfo.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public class BeingAliveLanguageInfo : GH_AssemblyInfo {
1919
public override GH_LibraryLicense License => GH_LibraryLicense.opensource;
2020

2121
// public override string AssemblyVersion => GetType().Assembly.GetName().Version.ToString();
22-
public override string AssemblyVersion => "1.0.0";
22+
public override string AssemblyVersion => "1.0.1";
2323

2424
// this is currently the variable used by McNeel for plugin system
2525
public override string Version => AssemblyVersion;

0 commit comments

Comments
 (0)