#region Using Statements
using System;
using System.Collections.Generic;
using System.Drawing;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using AViD.Figures;
using System.ComponentModel;
using System.Threading;
using System.IO;
#endregion Using Statements
namespace AViD
{
/// <summary>
/// The CoreType defines what sort of core is used for the dendrimer.
/// </summary>
public enum CoreType
{
Linear,
Trigonal,
Tetrahedral,
}
/// <summary>
/// Indicates what dimensions the dendrimer should use (ie. 2D or 3D).
/// </summary>
public enum Dimension
{
TwoDimensional,
ThreeDimensional
}
/// <summary>
/// The DendrimerBuilder class constructs the dendrimer with the provided inputs asynchronously.
/// </summary>
public class DendrimerBuilder : IWorker
{
#region Public Static Methods
/// <summary>
/// Takes the provided color and produces a darker color for a shadow.
/// </summary>
/// <param name="color">The color to base off the darker 'shadow' color.</param>
/// <returns>The shadow <see cref="Color"/>.</returns>
public static Color GetShadowColor(Color color)
{
return Color.FromArgb(
color.A,
Math.Max(color.R - _AMOUNT_OF_SHADOW_DARKNESS, 0),
Math.Max(color.G - _AMOUNT_OF_SHADOW_DARKNESS, 0),
Math.Max(color.B - _AMOUNT_OF_SHADOW_DARKNESS, 0));
}
#endregion Public Static Methods
#region Constants
/// <summary>
/// Provides the maximum number of generations for the <see cref="DendrimerFactor"/>
/// to produce of a Dendrimer.
/// </summary>
public const int MAX_NUMBER_OF_GENERATIONS = 6;
/// <summary>
/// Defines how much the original color is added to to get the shadow color.
/// </summary>
private const int _AMOUNT_OF_SHADOW_DARKNESS = 50;
/// <summary>
/// Determines the number of colors required, which is one more than the max number of generations (one for the core).
/// </summary>
private const int _REQUIRED_NUMBER_OF_COLORS = DendrimerBuilder.MAX_NUMBER_OF_GENERATIONS + 1;
/// <summary>
/// The threshold used to compare two vectors to see if they are indeed the same. We have to use this
/// because floating point arithmetic operations are known to have very small amounts of error.
/// </summary>
private const float _COMPARISON_THRESHOLD = 0.9999f;
/// <summary>
/// The default time we'll wait on the thread after we've aborted it.
/// </summary>
private const int _DEFAULT_JOIN_TIME = 5000;
/// <summary>
/// Radius of the spheres which make up the ball figures in two dimensions.
/// </summary>
private const float _RADIUS_OF_BALL_TWO_DIMENSIONAL = 0.75f;
/// <summary>
/// Radius of the spheres which make up the ball figures in three dimensions.
/// </summary>
private const float _RADIUS_OF_BALL_THREE_DIMENSIONAL = 1.0f;
/// <summary>
/// Radius of the cylinders which make up the stick figures in two dimensions.
/// </summary>
private const float _RADIUS_OF_STICK_TWO_DIMENSIONAL = .20f;
/// <summary>
/// Radius of the cylinders which make up the stick figures in three dimensions.
/// </summary>
private const float _RADIUS_OF_STICK_THREE_DIMENSIONAL = .50f;
/// <summary>
/// The error message used whenever an exception is thrown when
/// needed DirectX files are not found.
/// </summary>
private const string @_DIRECT_X_EXCEPTION_MESSAGE = "Unable to load necessary DirectX files, please read the 'System Requirements' section in the help documentation to determine how to resolve this issue.";
#endregion Constants
#region Constructor
/// <summary>
/// Constructs a new <see cref="DendrimerBuilder"/> instance to construct a dendrimer.
/// </summary>
/// <param name="device">The device for which this will be rendered for.</param>
/// <param name="coreType">The type of the core.</param>
/// <param name="intNumberOfChildren">The number of children at each subsequent generation.</param>
/// <param name="dendrimerDimension">The dimensions to use when creating the dendrimer model.</param>
/// <param name="clrGenerationColors">The colors to use for each generation, must match the number of generations.</param>
/// <param name="fltStartingLength">The starting length of the sticks.</param>
public DendrimerBuilder(Device device, CoreType coreType, int intNumberOfChildren, Dimension dendrimerDimension, Color[] clrGenerationColors, float fltStartingLength)
{
// Validate we have enough colors.
if (clrGenerationColors.Length < DendrimerBuilder._REQUIRED_NUMBER_OF_COLORS)
throw new ArgumentException("The number of generation colors is insufficient.", "clrGenerationColors");
this.m_blnAbortBuild = false;
this.m_blnEnableEvents = true;
this.m_Handlers = new EventHandlerList();
this.m_Thread = null;
this.m_Device = device;
this.m_coreType = coreType;
this.m_intNumberOfChildren = intNumberOfChildren;
this.m_dendrimerDimension = dendrimerDimension;
this.m_clrGenerationColors = clrGenerationColors;
this.m_fltStartingLength = fltStartingLength;
// Get the 'ambient' (or colors for the shadows) for the figures.
this.m_clrAmbientGenerationColors = this._GetAmbientGenerationColors();
// Create the length multipliers array.
if (dendrimerDimension == Dimension.TwoDimensional)
this.m_arLengthMultipliers = new float[DendrimerBuilder.MAX_NUMBER_OF_GENERATIONS + 1]
{
1.000f,
1.000f,
0.750f,
0.300f,
0.120f,
0.050f,
0.030f,
};
else
this.m_arLengthMultipliers = new float[DendrimerBuilder.MAX_NUMBER_OF_GENERATIONS + 1]
{
1.000f,
1.000f,
0.550f,
0.300f,
0.150f,
0.110f,
0.090f,
};
// Determine the number of figures that will be created.
this.m_intTotalFiguresToCreate = this._GetTotalFigureCount();
}
#endregion Constructor
#region Public Properties
/// <summary>
/// Gets/sets whether or not the events are enabled.
/// </summary>
public bool EnableEvents
{
get
{
lock (this.m_objThreadLock)
{
return this.m_blnEnableEvents;
}
}
set
{
lock (this.m_objThreadLock)
{
if (!this.IsWorking)
{
this.m_blnEnableEvents = value;
}
}
}
}
/// <summary>
/// Gets whether or not the DendrimerBuilder is working.
/// </summary>
public bool IsWorking
{
get
{
lock (this.m_objThreadLock)
{
return ((this.m_Thread != null) && this.m_Thread.IsAlive);
}
}
}
/// <summary>
/// Gets the produced <see cref="Figure"/> representing the requested dendrimer.
/// It is only available after the <see cref="DendrimerBuilder"/> has completed
/// its work.
/// </summary>
public Figure Dendrimer
{
get
{
lock (this.m_objThreadLock)
{
return this.m_Dendrimer;
}
}
protected set
{
lock (this.m_objThreadLock)
{
this.m_Dendrimer = value;
}
}
}
/// <summary>
/// Gets the <see cref="Dimension"/> of the <see cref="Figure"/> built.
/// </summary>
public Dimension DendrimerDimension
{
get
{
return this.m_dendrimerDimension;
}
}
/// <summary>
/// Gets the exception that was produced (if any) during the thread's execution.
/// </summary>
public Exception Exception
{
get
{
lock (this.m_objThreadLock)
{
return this.m_Exception;
}
}
protected set
{
lock (this.m_objThreadLock)
{
this.m_Exception = value;
}
}
}
#endregion Public Properties
#region Public Methods
/// <summary>
/// Attempts to abort the current task.
/// </summary>
public void Abort()
{
Thread thread;
if (this.IsWorking)
{
// Attempt to inform the thread to abort (this is more graceful than an abort call on the thread).
this.AbortBuild = true;
if ((thread = this.Thread) != null)
{
// Attempt to join on the thread, waiting for it to end.
thread.Join(DendrimerBuilder._DEFAULT_JOIN_TIME);
}
// Clear the thread out.
this.Thread = null;
}
}
/// <summary>
/// Performs the build of the dendrimer asynchronously.
/// </summary>
public void Work()
{
lock (this.m_objThreadLock)
{
if (this.IsWorking && !this.AbortBuild)
throw new ArgumentException("Existing thread still exists, thus the build was aborted.");
else
{
// Clear the abort work flag.
this.AbortBuild = false;
this.m_Thread = new Thread(new ThreadStart(this._Build));
this.m_Thread.Start();
}
}
}
#endregion Public Methods
#region Events/Delegates
/// <summary>
/// Handles the event at which the DendrimerBuilder is done.
/// </summary>
public event EventHandler Complete
{
add
{
lock (this.m_objThreadLock)
{
this.m_Handlers.AddHandler(this.m_objCompleteEventHandler, value);
}
}
remove
{
lock (this.m_objThreadLock)
{
this.m_Handlers.RemoveHandler(this.m_objCompleteEventHandler, value);
}
}
}
/// <summary>
/// Handles the event at which the DendrimerBuilder has a progress message for an external body.
/// </summary>
public event EventHandler<ProgressEventArgs> Progress
{
add
{
lock (this.m_objThreadLock)
{
this.m_Handlers.AddHandler(this.m_objProgressEventHandler, value);
}
}
remove
{
lock (this.m_objThreadLock)
{
this.m_Handlers.RemoveHandler(this.m_objProgressEventHandler, value);
}
}
}
#endregion Events/Delegates
#region Protected Members
/// <summary>
/// Gets/sets the flag to indicate the build needs to be aborted.
/// </summary>
protected bool AbortBuild
{
get
{
lock (this.m_objThreadLock)
{
return this.m_blnAbortBuild;
}
}
set
{
lock (this.m_objThreadLock)
{
this.m_blnAbortBuild = value;
}
}
}
/// <summary>
/// Gets/sets the <see cref="Thread"/> used to execute the build asynchronously.
/// </summary>
protected Thread Thread
{
get
{
lock (this.m_objThreadLock)
{
return this.m_Thread;
}
}
set
{
lock (this.m_objThreadLock)
{
this.m_Thread = value;
}
}
}
#endregion Protected Members
#region Protected Methods
/// <summary>
/// Notifies any listeners of what the current progress is.
/// </summary>
protected void SendProgress()
{
double dblPercentComplete;
if (this.m_intTotalFiguresToCreate > 0)
dblPercentComplete = (double)this.m_intFiguresCreated / (double)this.m_intTotalFiguresToCreate;
else
dblPercentComplete = 1.0d;
this.OnProgress(new ProgressEventArgs(Math.Max(0.0d, Math.Min(dblPercentComplete, 1.0d))));
}
/// <summary>
/// Handles firing the Progress event for the DendrimerBuilder.
/// </summary>
/// <param name="e">The <see cref="ProgressEventArgs"/> arguments for the event.</param>
protected void OnProgress(ProgressEventArgs e)
{
EventHandler<ProgressEventArgs> handler;
if (this.EnableEvents)
{
if ((handler = (this.m_Handlers[this.m_objProgressEventHandler] as EventHandler<ProgressEventArgs>)) != null)
handler(this, e);
}
}
/// <summary>
/// Handles firing the Complete event for the Worker.
/// </summary>
protected void OnComplete()
{
EventHandler handler;
if (this.EnableEvents)
{
if ((handler = (this.m_Handlers[this.m_objCompleteEventHandler] as EventHandler)) != null)
handler(this, EventArgs.Empty);
}
}
#endregion Protected Methods
#region Private Members
private object m_objThreadLock = new object();
private object m_objCompleteEventHandler = new object();
private object m_objProgressEventHandler = new object();
private bool m_blnAbortBuild;
private bool m_blnEnableEvents;
private EventHandlerList m_Handlers;
private Thread m_Thread;
private Exception m_Exception;
private readonly Device m_Device;
private readonly CoreType m_coreType;
private readonly int m_intNumberOfChildren;
private readonly Dimension m_dendrimerDimension;
private readonly Color[] m_clrGenerationColors;
private readonly Color[] m_clrAmbientGenerationColors;
private readonly float m_fltStartingLength;
private readonly float[] m_arLengthMultipliers;
private Figure m_Dendrimer;
private int m_intTotalFiguresToCreate;
private int m_intFiguresCreated;
#endregion Private Members
#region Private Methods
/// <summary>
/// Performs the build of the dendrimer.
/// </summary>
private void _Build()
{
Ball core;
// Initialize the build.
this.Dendrimer = core = null;
this.m_intFiguresCreated = 0;
try
{
// Create the core.
core = new Ball(
0,
this.m_Device,
Vector3.Empty,
(this.m_dendrimerDimension == Dimension.TwoDimensional) ?
DendrimerBuilder._RADIUS_OF_BALL_TWO_DIMENSIONAL :
DendrimerBuilder._RADIUS_OF_BALL_THREE_DIMENSIONAL,
this.m_clrGenerationColors[0],
this.m_clrAmbientGenerationColors[0]);
++this.m_intFiguresCreated;
// Notify any listeners of the progress we've made.
this.SendProgress();
if (this.AbortBuild)
return;
if ((core.Generation + 1) <= DendrimerBuilder.MAX_NUMBER_OF_GENERATIONS)
{
// Iterate over each of the vectors necessary for the core type.
foreach (Vector3 vecDirection in this._GetCoreVectors(this.m_coreType))
{
this._ExpandGeneration(core, core.Position, vecDirection, vecDirection);
if (this.AbortBuild)
return;
}
}
// Expose the finished dendrimer to the world.
this.Dendrimer = core;
}
catch (TypeInitializationException e)
{
// In the event a user doesn't have all the DirectX files installed, they'll get this exception.
this.Exception = new ApplicationException(
string.Format("{0}{1}{1}", _DIRECT_X_EXCEPTION_MESSAGE, Environment.NewLine),
e);
}
catch (Exception e)
{
// Save off this exception.
this.Exception = e;
}
finally
{
// Let the calling body know the work is done.
this.OnComplete();
}
}
/// <summary>
/// Expands the current ball with subsequent generations using the provided parameters.
/// </summary>
/// <param name="parent">The parent figure from which these figures will be children.</param>
/// <param name="vecPosition">The starting position of the new generation.</param>
/// <param name="vecDirection">The direction for the current generation to take.</param>
/// <param name="vecCore">The vector for the originating core vector.</param>
private void _ExpandGeneration(Figure parent, Vector3 vecPosition, Vector3 vecDirection, Vector3 vecCore)
{
Vector3 vecEndingPoint;
Color clrDiffuseColor, clrAmbientColor;
float fltLength;
int intCurrentGeneration;
Figure newParent;
Stick stick;
Ball ball;
intCurrentGeneration = parent.Generation + 1;
fltLength = this.m_arLengthMultipliers[intCurrentGeneration] * this.m_fltStartingLength;
// Find the end point and the line color.
vecEndingPoint = vecPosition + (vecDirection * fltLength);
clrDiffuseColor = this.m_clrGenerationColors[intCurrentGeneration];
clrAmbientColor = this.m_clrAmbientGenerationColors[intCurrentGeneration];
// Create the stick.
stick = new Stick(
intCurrentGeneration,
this.m_Device,
vecPosition,
vecDirection,
fltLength,
(this.m_dendrimerDimension == Dimension.TwoDimensional) ?
DendrimerBuilder._RADIUS_OF_STICK_TWO_DIMENSIONAL :
DendrimerBuilder._RADIUS_OF_STICK_THREE_DIMENSIONAL,
clrDiffuseColor,
clrAmbientColor);
// Add it to the parent and set it as the new parent
parent.AddChild(stick);
++this.m_intFiguresCreated;
newParent = stick;
// If we're in three dimensions, add a ball at the end.
if (this.m_dendrimerDimension == Dimension.ThreeDimensional)
{
ball = new Ball(
intCurrentGeneration,
this.m_Device,
vecEndingPoint,
(this.m_dendrimerDimension == Dimension.TwoDimensional) ?
DendrimerBuilder._RADIUS_OF_BALL_TWO_DIMENSIONAL :
DendrimerBuilder._RADIUS_OF_BALL_THREE_DIMENSIONAL,
clrDiffuseColor,
clrAmbientColor);
stick.AddChild(ball);
++this.m_intFiguresCreated;
newParent = ball;
}
// Notify any listeners of the progress we've made.
this.SendProgress();
if (this.AbortBuild)
return;
// Recurse to find the other generations, if needed.
if ((intCurrentGeneration + 1) <= DendrimerBuilder.MAX_NUMBER_OF_GENERATIONS)
{
// Make sure the direction isn't where we came from (we have a threshold because of imprecision in floats)
foreach (Vector3 vecChild in this._GetChildVectors(vecDirection, vecCore))
{
this._ExpandGeneration(newParent, vecEndingPoint, vecChild, vecCore);
if (this.AbortBuild)
return;
}
}
}
/// <summary>
/// Gets the vectors that will come off from the core of the dendrimer.
/// </summary>
/// <param name="coreType">The type of the core.</param>
/// <returns>The vectors that will come off from the core of the dendrimer.</returns>
private Vector3[] _GetCoreVectors(CoreType coreType)
{
switch (coreType)
{
case CoreType.Linear:
return new Vector3[]
{
new Vector3(1f, 0f, 0f),
new Vector3(-1f, 0f, 0f),
};
case CoreType.Trigonal:
return new Vector3[]
{
new Vector3(1f, 0f, 0f),
new Vector3(-.5f, (-(float)Math.Sqrt(3f) / 2f), 0f),
new Vector3(-.5f, ((float)Math.Sqrt(3f) / 2f), 0f),
};
case CoreType.Tetrahedral:
if (this.m_dendrimerDimension == Dimension.TwoDimensional)
return new Vector3[]
{
new Vector3(1f, 0f, 0f),
new Vector3(-1f, 0f, 0f),
new Vector3(0f, 1f, 0f),
new Vector3(0f, -1f, 0f),
};
else /* if (this.m_dendrimerDimension == Dimension.ThreeDimensional) */
return new Vector3[]
{
new Vector3(1f, 0f, 0f),
new Vector3((-1f / 3f), (-(float)Math.Sqrt(2f) / 3f), (float)Math.Sqrt(2f / 3f)),
new Vector3((-1f / 3f), ((2f * (float)Math.Sqrt(2f)) / 3f), 0f),
new Vector3((-1f / 3f), (-(float)Math.Sqrt(2f) / 3f), -(float)Math.Sqrt(2f / 3f)),
};
default:
throw new NotImplementedException(string.Format("CoreType, {0}, has not been implemented yet.", coreType.ToString()));
}
}
/// <summary>
/// Gets the vectors that will come off from subsequent generations.
/// </summary>
/// <param name="vecInitialDirection">The initial vector for which we'll use to determine rotation.</param>
/// <param name="vecCore">The vector for the originating core vector.</param>
/// <returns>The vectors that will come off from the core of the dendrimer.</returns>
private Vector3[] _GetChildVectors(Vector3 vecInitialDirection, Vector3 vecCore)
{
Vector3[] vectors;
Matrix matrix;
Vector3 vecStarting, vecAxisToRotate;
float fltComparison;
int intIndex;
// To ensure that the code generates child vectors that are within the same plane
// as the core's vector, when two children are expected and in 3d, I'm handling it specially here.
if ((this.m_dendrimerDimension == Dimension.ThreeDimensional) && (this.m_intNumberOfChildren == 2))
{
// Find the axis to rotate along that is perpendicular to the core vector
// and the 'up vector'. It will allow us to keep within the same plane
// for all child vectors.
vecAxisToRotate = Vector3.Cross(vecCore, new Vector3(0f, 0f, 1f));
// Rotate the initial vector by PI/3 and -PI/3 to get the two child vectors.
vectors = new Vector3[]
{
Vector3.TransformCoordinate(vecInitialDirection, Matrix.RotationAxis(vecAxisToRotate, (float)Math.PI/3.0f)),
Vector3.TransformCoordinate(vecInitialDirection, Matrix.RotationAxis(vecAxisToRotate, -(float)Math.PI/3.0f))
};
}
else
{
switch (this.m_intNumberOfChildren)
{
case 1:
vectors = new Vector3[]
{
new Vector3(1f, 0f, 0f),
};
break;
case 2:
vectors = new Vector3[]
{
new Vector3(.5f, ((float)Math.Sqrt(3f) / 2f), 0f),
new Vector3(.5f, (-(float)Math.Sqrt(3f) / 2f), 0f),
};
break;
case 3:
if (this.m_dendrimerDimension == Dimension.TwoDimensional)
vectors = new Vector3[]
{
new Vector3(1f, 0f, 0f),
new Vector3(.5f, (float)Math.Sqrt(3f) / 2f, 0f),
new Vector3(.5f, (float)Math.Sqrt(3f) / -2f, 0f),
};
else /* if (this.m_dendrimerDimension == Dimension.ThreeDimensional) */
vectors = new Vector3[]
{
new Vector3((1f / 3f), ((float)Math.Sqrt(2f) / 3f), (float)Math.Sqrt(2f / 3f)),
new Vector3((1f / 3f), ((-2f * (float)Math.Sqrt(2f)) / 3f), 0f),
new Vector3((1f / 3f), ((float)Math.Sqrt(2f) / 3f), -(float)Math.Sqrt(2f / 3f)),
};
break;
default:
throw new NotImplementedException(string.Format("NumberOfChildren, {0}, has not been implemented yet.", this.m_intNumberOfChildren.ToString()));
}
// Assume the starting vector will be (1, 0, 0) because all the vectors assume a starting point
// of (1, 0, 0).
vecStarting = new Vector3(1f, 0f, 0f);
// Determine if the two vectors are very close to each other. If they're not, then we can rotate freely.
if (Math.Abs(fltComparison = Vector3.Dot(vecStarting, vecInitialDirection)) < DendrimerBuilder._COMPARISON_THRESHOLD)
{
matrix = Utility.ComputeRotationMatrix(vecStarting, vecInitialDirection);
// Rotate each vector by the rotational matrix.
for (intIndex = 0; intIndex < vectors.Length; ++intIndex)
vectors[intIndex] = Vector3.TransformNormal(vectors[intIndex], matrix);
}
else if (fltComparison < 0)
{
// If we're less than zero, the vector is very close and in the opposite direction.
// Just negate the vectors to rotate them properly.
for (intIndex = 0; intIndex < vectors.Length; ++intIndex)
vectors[intIndex] = vectors[intIndex] * -1;
}
}
return vectors;
}
/// <summary>
/// Gets the ambient (or shadow) generation colors for the dendrimer.
/// </summary>
/// <returns>The array of ambient generation colors we produced from the generation colors.</returns>
private Color[] _GetAmbientGenerationColors()
{
Color[] clrAmbientGenerationColors;
clrAmbientGenerationColors = new Color[this.m_clrGenerationColors.Length];
for (int i = 0; i < this.m_clrGenerationColors.Length; ++i)
clrAmbientGenerationColors[i] = DendrimerBuilder.GetShadowColor(this.m_clrGenerationColors[i]);
return clrAmbientGenerationColors;
}
/// <summary>
/// Determines the total number of figures that will comprise the Dendrimer being created.
/// </summary>
/// <returns>The total number of figures that will comprise the Dendrimer being created.</returns>
private int _GetTotalFigureCount()
{
int intGenerationOneGrowthFactor, intFiguresPerGeneration;
// Handle the first generation directly attached to the core.
if (DendrimerBuilder.MAX_NUMBER_OF_GENERATIONS > 0)
{
switch (this.m_coreType)
{
case CoreType.Linear:
intGenerationOneGrowthFactor = 2;
break;
case CoreType.Trigonal:
intGenerationOneGrowthFactor = 3;
break;
case CoreType.Tetrahedral:
intGenerationOneGrowthFactor = 4;
break;
default:
throw new NotImplementedException(string.Format("CoreType, {0}, has not been implemented yet.", this.m_coreType.ToString()));
}
}
intFiguresPerGeneration = (this.m_dendrimerDimension == Dimension.TwoDimensional) ? 1 : 2;
// Proof of Correctness
//
// F1 = intGenerationOneGrowthFactor
// F2 = this.m_intNumberOfChildren
// n = Number of Generations (DendrimerBuilder.MAX_NUMBER_OF_GENERATIONS)
// p = Number of figures per generation
//
// Series: Sn = 1 + (p * F1) + (p * F1 * F2) + (p * F1 * F2^2) + (p * F1 * F2^3) + ... + (p * F1 * F2^n)
// = 1 + [p * F1 * (1 + F2 + F2^2 + F2^3 + ... + F2^n)]
// = 1 + [p * F1 * ((1 - F2^n) / (1 - F2))]
if (this.m_intNumberOfChildren > 1)
return (1 + (intFiguresPerGeneration * intGenerationOneGrowthFactor * ((1 - ((int)Math.Pow(this.m_intNumberOfChildren, DendrimerBuilder.MAX_NUMBER_OF_GENERATIONS)))
/ (1 - this.m_intNumberOfChildren))));
else
return (1 + (intFiguresPerGeneration * intGenerationOneGrowthFactor * DendrimerBuilder.MAX_NUMBER_OF_GENERATIONS));
}
#endregion Private Methods
}
}