Editor-side placement tools for a mapping addon. Contains an abstract PartBuildingEditor that turns a rectangle drawn with three clicks into a generated building, plus concrete tools for an apartment block, a warehouse, switchback stairs and a pipe run, each with its own settings and material picked from the tool surface. Geometry comes from IPartGenerator implementations through PartService.Plan for previews and PartTool.Place for the final objects.
namespace AdvancedMappingTools;
/// <summary>
/// A building of independent parts on a rectangle set by three clicks.
/// </summary>
public abstract class PartBuildingEditor( PrimitiveTool tool ) : RectEditor( tool )
{
protected abstract IPartGenerator Generator { get; }
protected abstract GeneratorSettings Parts { get; }
public override object ExtraSettings => Parts;
protected override bool PlacesObjects => true;
protected override void BuildMesh( MeshKit kit ) { }
/// <summary>
/// Give the settings the size of the rectangle and the materials of the tool.
/// </summary>
protected abstract void Prepare();
/// <summary>Where the parts stand: the drawn rectangle, or more around it for a roof that covers walls.</summary>
protected virtual Transform PlacementFrame => RectFrame;
protected override PolygonMesh BuildPreview()
{
Prepare();
var frame = PlacementFrame;
return PartService.Plan( Generator, Parts, surface: SurfaceIntegration.Context( Parts, SceneEditorSession.Active?.Scene, frame ) ).Flatten( frame );
}
protected override void PlaceObjects()
{
Prepare();
PartTool.Place( Generator, GeneratorRegistry.Load( Generator, GeneratorRegistry.Save( Parts ) ), PlacementFrame );
}
}
/// <summary>
/// A block of flats with balconies, an entrance and a roof full of equipment.
/// </summary>
[ToolCategory( "Buildings" )]
[Title( "Apartment Block" ), Icon( "domain" )]
public sealed class ApartmentEditor( PrimitiveTool tool ) : PartBuildingEditor( tool )
{
readonly ApartmentGenerator _generator = new();
/// <summary>New buildings get profiled window frames; placed ones keep what they were saved with.</summary>
public ApartmentSettings Settings { get; } = new() { ProfiledFrames = true };
protected override IPartGenerator Generator => _generator;
protected override GeneratorSettings Parts => Settings;
protected override void Prepare()
{
Settings.Width = RectLength;
Settings.Depth = RectDepth;
Settings.WallMaterial = PartTool.Pick( Settings.WallMaterial, SurfaceMaterial );
Settings.TrimMaterial = PartTool.Pick( Settings.TrimMaterial, TrimMaterial );
Settings.FloorMaterial = PartTool.Pick( Settings.FloorMaterial, SurfaceMaterial );
Settings.GlassMaterial = PartTool.Pick( Settings.GlassMaterial, TrimMaterial );
}
}
/// <summary>
/// A steel hall with roller doors, cladding and a loading dock.
/// </summary>
[ToolCategory( "Buildings" )]
[Title( "Warehouse" ), Icon( "warehouse" )]
public sealed class WarehouseEditor( PrimitiveTool tool ) : PartBuildingEditor( tool )
{
readonly WarehouseGenerator _generator = new();
public WarehouseSettings Settings { get; } = new();
protected override IPartGenerator Generator => _generator;
protected override GeneratorSettings Parts => Settings;
protected override void Prepare()
{
Settings.Width = RectLength;
Settings.Depth = RectDepth;
Settings.WallMaterial = PartTool.Pick( Settings.WallMaterial, SurfaceMaterial );
Settings.SteelMaterial = PartTool.Pick( Settings.SteelMaterial, TrimMaterial );
Settings.RoofMaterial = PartTool.Pick( Settings.RoofMaterial, SurfaceMaterial );
Settings.ConcreteMaterial = PartTool.Pick( Settings.ConcreteMaterial, SurfaceMaterial );
}
}
/// <summary>
/// Stairs that turn back at every landing. The two clicks set one flight.
/// </summary>
[ToolCategory( "Stairs & Ramps" )]
[Title( "Switchback Stairs" ), Icon( "stairs" )]
public sealed class SwitchbackEditor( PrimitiveTool tool ) : LineEditor( tool )
{
readonly SwitchbackGenerator _generator = new();
public SwitchbackSettings Settings { get; } = new();
public override object ExtraSettings => Settings;
protected override bool PlacesObjects => true;
protected override void BuildMesh( MeshKit kit ) { }
void Prepare()
{
Settings.Run = Length;
Settings.StepMaterial = PartTool.Pick( Settings.StepMaterial, SurfaceMaterial );
Settings.TrimMaterial = PartTool.Pick( Settings.TrimMaterial, TrimMaterial );
}
protected override PolygonMesh BuildPreview()
{
Prepare();
return PartService.Plan( _generator, Settings ).Flatten( LineFrame );
}
protected override void PlaceObjects()
{
Prepare();
PartTool.Place( _generator, Settings, LineFrame );
}
}
/// <summary>
/// Pipes, ducts and cable trays along a line, with flanges and posts.
/// </summary>
[ToolCategory( "Industrial" )]
[Title( "Pipe Run" ), Icon( "plumbing" )]
public sealed class PipeEditor : PathEditor
{
public override IReadOnlySet<string> UnusedOptions { get; } = new HashSet<string> { "CurveSegments", "Variation" };
readonly PipeGenerator _generator = new();
public PipeEditor( PrimitiveTool tool ) : base( tool ) { }
public PipeSettings Settings { get; } = new();
public override object ExtraSettings => Settings;
protected override int MinPoints => 2;
protected override bool PlacesObjects => true;
protected override bool AllowsHeight => true;
protected override IEnumerable<string> Hints => [.. base.Hints, "Shift + drag a point: raise or lower it"];
protected override void BuildMesh( MeshKit kit ) { }
void Prepare()
{
Settings.Path = Curve();
Settings.PipeMaterial = PartTool.Pick( Settings.PipeMaterial, SurfaceMaterial );
Settings.TrimMaterial = PartTool.Pick( Settings.TrimMaterial, TrimMaterial );
}
protected override PolygonMesh BuildPreview()
{
Prepare();
return PartService.Plan( _generator, Settings ).Flatten( new Transform( Vector3.Zero, Rotation.Identity ) );
}
protected override void PlaceObjects()
{
Prepare();
PartTool.Place( _generator, Settings, new Transform( Vector3.Zero, Rotation.Identity ) );
}
}