Editor-only procedural tree tool. Defines a TreeSettings prop with kind and height, a TreeGenerator that builds palm, olive, cypress, bush and pine shapes from primitives, and a TreeEditor primitive tool wrapper.
namespace AdvancedMappingTools;
public enum TreeKind
{
Palm,
Olive,
[Title( "Cypress" )] Cypress,
Pine,
Bush
}
public sealed class TreeSettings : PropSettings
{
[Property, Group( "Tree" ), WideMode]
public TreeKind Kind { get; set; } = TreeKind.Palm;
[Property, Group( "Tree" ), Range( 32, 2048, slider: false ), WideMode]
public float Height { get; set; } = 320.0f;
public override string PropName => Kind.ToString();
}
/// <summary>
/// A stylised Mediterranean tree: palm, olive, cypress or pine. Trunk and crown are separate
/// parts, so a tree can be edited or replaced part by part.
/// </summary>
public sealed class TreeGenerator : PropGenerator
{
public override string Id => "tree";
public override string Title => "Tree";
public override Type SettingsType => typeof( TreeSettings );
protected override void Build( PropSettings settings, PropBuilder b )
{
var s = (TreeSettings)settings;
var height = MathF.Max( s.Height, 32.0f );
switch ( s.Kind )
{
case TreeKind.Palm: Palm( b, height ); break;
case TreeKind.Olive: Olive( b, height ); break;
case TreeKind.Cypress: Cypress( b, height ); break;
case TreeKind.Bush: Bush( b, height ); break;
default: Pine( b, height ); break;
}
}
static void Palm( PropBuilder b, float height )
{
b.Round( "trunk", "Trunk", b.Main, 0, 0, 5.5f, 2.5f, 0.0f, height );
var top = height;
var fronds = 9;
for ( var i = 0; i < fronds; i++ )
{
var angle = i * MathF.PI * 2.0f / fronds;
var cos = MathF.Cos( angle );
var sin = MathF.Sin( angle );
var start = new Vector3( 0.0f, 0.0f, top );
var mid = new Vector3( cos * height * 0.18f, sin * height * 0.18f, top - height * 0.05f );
var tip = new Vector3( cos * height * 0.3f, sin * height * 0.3f, top - height * 0.2f );
b.Loose( $"frond{i}", "Frond", b.Detail, kit =>
{
kit.Beam( start, mid, 3.0f, 1.2f );
kit.Beam( mid, tip, 2.2f, 0.8f );
} );
}
b.LooseBox( "coconuts", "Coconuts", b.Metal, new Vector3( -6, -6, top - 10 ), new Vector3( 6, 6, top - 2 ) );
}
static void Olive( PropBuilder b, float height )
{
b.Round( "trunk", "Trunk", b.Main, 0, 0, 7.0f, 4.0f, 0.0f, height * 0.5f );
b.Round( "crown_low", "Crown", b.Detail, 0, 0, height * 0.28f, height * 0.32f, height * 0.42f, height * 0.62f, 12 );
b.Round( "crown_mid", "Crown", b.Detail, 0, 0, height * 0.34f, height * 0.3f, height * 0.52f, height * 0.78f, 12 );
b.Round( "crown_top", "Crown", b.Detail, 0, 0, height * 0.24f, height * 0.1f, height * 0.7f, height * 0.92f, 12 );
}
static void Cypress( PropBuilder b, float height )
{
b.Round( "trunk", "Trunk", b.Main, 0, 0, 5.0f, 4.0f, 0.0f, height * 0.2f );
b.Round( "body", "Cypress", b.Detail, 0, 0, height * 0.12f, 1.0f, height * 0.12f, height, 14 );
}
static void Bush( PropBuilder b, float height )
{
var r = height * 0.45f;
b.Round( "crown_low", "Bush", b.Detail, 0, 0, r, r * 0.8f, 0.0f, height * 0.45f, 10 );
b.Round( "crown_mid", "Bush", b.Detail, r * 0.35f, -r * 0.2f, r * 0.75f, r * 0.6f, height * 0.3f, height * 0.7f, 10 );
b.Round( "crown_top", "Bush", b.Detail, -r * 0.3f, r * 0.25f, r * 0.55f, r * 0.4f, height * 0.5f, height * 0.85f, 10 );
}
static void Pine( PropBuilder b, float height )
{
b.Round( "trunk", "Trunk", b.Main, 0, 0, 8.0f, 5.0f, 0.0f, height * 0.4f );
b.Round( "crown_low", "Crown", b.Detail, 0, 0, height * 0.22f, height * 0.12f, height * 0.25f, height * 0.58f, 14 );
b.Round( "crown_mid", "Crown", b.Detail, 0, 0, height * 0.18f, height * 0.08f, height * 0.46f, height * 0.76f, 14 );
b.Round( "crown_top", "Crown", b.Detail, 0, 0, height * 0.12f, 1.0f, height * 0.65f, height * 0.98f, 14 );
}
}
/// <summary>
/// Places a single tree. Palm, olive, cypress and pine are the four Mediterranean kinds.
/// </summary>
[ToolCategory( "Vegetation" )]
[Title( "Tree" ), Icon( "park" )]
public sealed class TreeEditor( PrimitiveTool tool ) : PropEditor( tool )
{
readonly TreeGenerator _generator = new();
public TreeSettings Settings { get; } = new();
protected override IPartGenerator Generator => _generator;
public override PropSettings Props => Settings;
}