Home Designer Suite

A comprehensive Unity toolkit for building home and interior design applications. Draw 2D floor plans, generate 3D buildings with procedural geometry, furnish interiors, and export your designs.

Unity 6+ Built-in / URP / HDRP Windows / Mac / Android / iOS Procedural Mesh Generation JSON Serialization GLB Export
160+
C# Scripts
20+
Controllers
30+
UI Components
9
Procedural Generators
3
Render Pipelines
63
Furniture Prefabs

Installation

Before opening the demo scenes, install the following packages via the Unity Package Manager (Window > Package Manager).

Input System

Required for all keyboard and touch input handling. Install from the Unity Registry:

  1. In the Package Manager, select Unity Registry from the dropdown.
  2. Search for Input System.
  3. Click Install. Unity will prompt you to enable the new input system and restart the editor — accept.

UniGLTF (required for GLB export)

Install from a Git URL:

  1. In the Package Manager, click + > Add package from git URL.
  2. Enter the following URL and click Add:
Git URL
https://github.com/vrm-c/UniVRM.git?path=/Packages/UniGLTF#v0.131.0
💡
After installing UniGLTF, add the UNIGLTF scripting define symbol in Edit > Project Settings > Player > Other Settings > Scripting Define Symbols to enable the GLB export feature.

Quick Start

Try the Demo Scene

The fastest way to get started is to open and run the Demo_Editor scene:

Assets/Exoa/HomeDesigner/Scenes/Demo_Editor.unity

This scene contains a fully working interior design editor — you can draw floor plans, paint materials, place furniture, and switch to 3D preview mode right away. It is designed to be used as-is or as a starting point: feel free to extend it, rearrange the UI, or strip it down to only the features your application needs.

💡
A second scene, Demo_API.unity, demonstrates how to create buildings entirely from code using HeadlessBuildingAPI — useful if you want to generate buildings programmatically without the editor UI.

License Activation

A valid license is required to use Home Designer in the Unity Editor. To activate:

  1. In the Unity menu bar, go to Exoa > Home Designer > Licensing.
  2. Enter your email address and your invoice number (from your Asset Store purchase).
  3. Click Activate.
If you experience any licensing issue, please contact support — include your email address and invoice number to speed up the process.

Home Designer Settings

The settings panel is the central place to configure the plugin. Open it from the menu bar:

Tools > Exoa > Home Designer > Settings

The panel is a direct reflection of the HomeDesignerSettings ScriptableObject. Any change made in the panel is immediately written to that asset. It exposes all the settings needed to set up the plugin, including the database of furniture modules and materials available to the designer at runtime.

💡
You can also select the HomeDesignerSettings asset directly in the Project window (HomeDesignerSettings) and edit it in the Inspector — both views show the same data.

GLB Export Setup (Optional)

When a project is saved, Home Designer can export the building as a GLB 3D model alongside the JSON file. This requires the UniGLTF package and a scripting define symbol.

💡
Once set up, the GLB export toggle appears in the HomeDesignerSettings ScriptableObject (HomeDesignerSettings). Enable it there to activate automatic GLB export on save.

Step 1 — Install UniGLTF

Open Window > Package Manager, click + > Add package from git URL, and enter the UniGLTF package URL. Alternatively install it via the .unitypackage file if you have a local copy.

Step 2 — Add the Scripting Define Symbol

Open Edit > Project Settings > Player > Other Settings > Scripting Define Symbols and add:

Symbol
UNIGLTF

Apply the change. Unity will recompile; GLB export code will be compiled in after that.

Step 3 — Enable in HomeDesignerSettings

Select the HomeDesignerSettings asset in the Project window. The Export GLB On Save option is now available — enable it to export a .glb file every time a project is saved.

Render Pipeline Setup

Home Designer supports Built-in RP, URP, and HDRP. After importing the package or switching your project's render pipeline, you must convert the built-in materials to match your pipeline using the Render Pipeline Switcher.

Using the Render Pipeline Switcher

Open it from the menu bar: Tools > Exoa > Render Pipeline Switcher.

The tool batch-converts all Home Designer materials (walls, floors, grid, doors, windows, ghost, exterior) to the shaders expected by your current render pipeline. Select your target pipeline in the popup and click Convert.

Module materials must be converted manually. The Render Pipeline Switcher only processes Home Designer's own built-in materials. Furniture and decoration prefabs in Samples/Modules/ use their own materials — you must convert these yourself, either through Edit > Render Pipeline > Upgrade Project Materials (URP/HDRP built-in utility) or by manually reassigning shaders on each material.

Per-Pipeline Notes

PipelineNotes
Built-in RPDefault. No extra setup required after conversion.
URPEnsure a URP Asset is assigned in Project Settings > Graphics before converting.
HDRPIf the grid is not visible, adjust the emissive intensity on the grid material to match your directional light settings.

Project Structure

Assets/Exoa/ ├── Documentation/ — Developer documentation (this file) └── HomeDesigner/ ├── Scripts/ │ ├── Controllers/ — AppController, BuildingController, FloorController, etc. │ ├── Core/ — HeadlessBuildingAPI, BuildingUIBridge, BuildingFactory, ServiceLocator, BuildingAPIExample │ ├── Procedural/ — ProceduralRoom, ProceduralArea, ProceduralOpening, ProceduralExterior, Roofs │ ├── Serialization/ — ProjectSerializer (JSON persistence) │ ├── Events/ — HomeDesignerEvents (central event hub) │ ├── UI/ — Menus, Popups, List Items, Faders │ ├── Modules/ — Furniture module data models & controller │ ├── Extensions/ — C# extension methods │ ├── Inputs/Touch/ — Touch input handling │ ├── Grid/ — Grid rendering & snapping │ ├── History/ — Undo/Redo manager │ ├── Settings/ — HomeDesignerSettings, HDConsts │ ├── Enums/ — Tags, Layers, Scenes │ ├── Maths/ — Acceleration, Integrals, Move, SpringMaths │ ├── Camera/ — CameraNoFog │ └── Thumbnails/ — ThumbnailGeneratorItem ├── Scenes/ — Demo_Editor.unity, Demo_API.unity ├── Packages/ │ ├── Toolkit/ — ProceduralToolkit, ClipperLib, LibTessDotNet │ ├── TouchCameraLite/ — Camera orbit/pan/zoom/focus controls │ ├── Json/ — Newtonsoft.Json (custom IL2CPP-compatible build) │ └── Outline/ — Object selection outline/highlight system ├── Resources/ — HomeDesignerSettings asset and prefabs ├── Prefabs/ — Building components, UI panels, input handlers ├── Samples/Modules/ — Furniture prefabs: Appliances, BedRoom, DoorsWindows, Kitchen, Lights, LivingRoom, Patio ├── Materials/ — Exterior, floor, grid, door/window, ghost materials ├── Shaders/ — Built-in RP, URP, HDRP, FX shaders ├── Editor/ — RenderPipelineSwitcher, HeadlessModuleAPI, custom inspectors └── Tests/Editor/ — Editor unit tests

Key Scenes

ScenePurposePath
Demo_EditorFull interactive editor — floor plan drawing, material painting, furniture placement, preview modeExoa/HomeDesigner/Scenes/
Demo_APIHeadless/programmatic API demo — creates buildings via HeadlessBuildingAPI from code without editor UI (BuildingAPIExample component with 4 example buttons)Exoa/HomeDesigner/Scenes/

Requirements

Unity Version

Unity 6 (6000.3.9f1) or later

Required Packages

Unity GLB Exporter (via Package Manager)

Target Platforms

Windows, Mac, Android, iOS

License System

The license check runs editor-only — it is stripped from all builds. Activating the license via Exoa > Home Designer > Licensing stores a token locally; no check occurs at runtime in a shipped application. If the license DLLs are deleted, a compile error is raised by design to prevent accidental removal.

💡
You do not need to ship any license files or API keys with your game. The license system has zero footprint in production builds.

Scripting Define Symbols

SymbolPurposeWhen to Use
UNIGLTFEnables GLB Export functionalityWhen UniGLTF package is installed

Integration Guide

If you want to embed Home Designer into an existing scene or project rather than building on top of Demo_Editor, follow the steps below.

Minimum Required Components

Your scene needs the following GameObjects at minimum to run the plugin:

ComponentPurpose
AppControllerState machine — drives all mode transitions. Singleton.
HDInputManagerReads keyboard/touch input and routes it to the event system. Attach alongside AppController.
HeadlessBuildingAPIFactory for creating buildings, floors, rooms, and openings. Singleton.
BuildingFactoryInstantiates building component prefabs and converts coordinates. Singleton.
ProjectSerializerHandles JSON save/load and undo/redo snapshots. Singleton.

The easiest approach is to copy the root GameObject hierarchy from Demo_Editor and strip out only the UI you don't need.

Adding the UI (Optional)

If you want the built-in editor panels, add the main UI Canvas prefab to your scene and ensure BuildingUIBridge is present. After creating a building call:

C#
BuildingUIBridge.Instance.ConnectBuildingToUI(building);

This wires every floor, room, and opening in the building to the corresponding UI list items. If you create buildings programmatically after scene load, call this once after building.Build().

Running Without UI

Omit the Canvas and BuildingUIBridge entirely. Use HeadlessBuildingAPI to create geometry and HomeDesignerEvents to react to state changes. This is the pattern used by Demo_API.

Multiple Buildings

Each BuildingController is an independent GameObject. Instantiate as many as you need. The system tracks one active building — switch with:

C#
BuildingController.SetCurrentBuilding(mySecondBuilding);

Editor operations (draw, paint, furnish) always apply to the active building.

💡
Assign the HomeDesignerSettings ScriptableObject to the AppController inspector field. Without it the plugin will throw a NullReferenceException on startup.

Architecture Overview

Home Designer follows an event-driven architecture with a centralized state machine. The core pattern: user input → events → controllers → procedural generation → 3D output.

⚙ State Management
  • AppController — singleton state machine
  • States drive UI visibility & behavior
  • State changes broadcast via delegate
⚡ Event System
  • HomeDesignerEvents — static Action delegates
  • No Unity messaging — all System.Action
  • OnRequest* for UI → logic, OnFile* for persistence
✎ Drawing & Control
  • ControlPointsController — point management
  • IObjectDrawer — common draw interface
  • Snap-to-grid and snap-to-pathlines
⚙ Procedural Generation
  • ProceduralToolkit + ClipperLib + LibTessDotNet
  • MeshDraft abstraction for mesh building
  • Rooms, openings, exterior shells, roofs
📁 Data Persistence Layer
  • ProjectSerializer — JSON save/load via Newtonsoft.Json (IL2CPP compatible)
  • UndoRedoManager — up to 50 JSON snapshots for undo/redo
  • HeadlessBuildingAPI — programmatic building creation from data

State Machine

AppController is a SingletonMonoBehaviour<AppController> managing the application lifecycle. Setting AppController.State fires the OnAppStateChange delegate.

Idle
Floors
FloorPlan
Paint
Furnish
Preview
StateDescriptionActive UI Panels
IdleNo project loaded; welcome screenProject wizard
FloorsFloor management — add/remove/duplicate floorsUIFloorsMenu
FloorPlanDraw rooms and openings on the 2D gridUIFloorPlanMenu
PaintApply materials to walls, floors, ceilingsMaterialPopup
FurnishPlace and arrange furniture modulesUIFurnishMenu
Preview3D preview with stacked floors at Y intervalsPreview controls
C#
// Subscribe to state changes
AppController.OnAppStateChange += (AppController.States newState) =>
{
    if (newState == AppController.States.FloorPlan)
        ShowFloorPlanUI();
};

// Change state programmatically
AppController.Instance.State = AppController.States.Preview;

Data Flow Pipeline

From user drawing to 3D geometry — the complete build pipeline:

1
User Draws
Control points on grid
2
Controllers
SpaceController / OpeningController wrap CPC
3
Data Model
FloorSpaceItemData / FloorOpeningItemData
4
Build Pipeline
BuildingController.Build() → FloorController.Build()
5
Procedural
ProceduralRoom.Generate() creates meshes
6
3D Output
Walls, floor, ceiling, colliders
ControlPointsController
IObjectDrawer
FloorController.Build()
Collect Openings
Build Spaces ×2
Build Openings
Build Exterior

Class Hierarchy

Controllers

Hierarchy
MonoBehaviour
├── SingletonMonoBehaviour<T>
│   ├── AppController                    // State machine
│   ├── ProjectSerializer              // JSON persistence
│   ├── HeadlessBuildingAPI              // Programmatic building API
│   ├── BuildingFactory                  // Prefab instantiation & coord conversion
│   └── BuildingUIBridge                 // Scene↔UI connection
├── BuildingComponentBase
│   ├── SpaceController : IObjectDrawer   // Base for rooms/outside
│   │   ├── RoomController               // Room management
│   │   └── OutsideController            // Outdoor areas
│   └── OpeningController : IObjectDrawer // Doors/windows/archways
├── BuildingController                   // Building root
├── FloorController                      // Per-floor management
├── ControlPointsController              // Point drawing system
└── ServiceLocator (static)             // Lightweight DI container

Procedural Generation

Hierarchy
BuildingComponentBase
├── ProceduralSpace (abstract)
│   ├── ProceduralRoom       // Walls, floor, ceiling, colliders, reflection probe
│   └── ProceduralOutside    // Outdoor area geometry
├── ProceduralArea           // Area visualization / overlay geometry
├── ProceduralOpening        // Door/window/archway meshes
├── ProceduralExterior       // Building shell from contours
└── ProceduralRoof (abstract, IConstructible<MeshDraft>)
    ├── ProceduralRoofFlat
    ├── ProceduralRoofHipped
    └── ProceduralRoofGabled

HeadlessBuildingAPI

SingletonMonoBehaviour<HeadlessBuildingAPI> — High-level API for creating buildings programmatically without UI dependencies. Used by deserialization, undo/redo, and headless creation scenarios.

💡
Access via HeadlessBuildingAPI.Instance. All methods can be called without any UI being present in the scene.

Building Creation

MethodReturnsDescription
CreateBuilding(string name, BuildingConstructionSettings settings) BuildingController Creates an empty building with construction settings
CreateBuilding(string name, UnifiedBuildingData data) BuildingController Creates a complete building from data (floors, rooms, openings)
CreateBuildingFromJson(string name, string json) BuildingController Deserializes JSON and creates building
GetBuildingJson(BuildingController building) string Serializes building to JSON string
GetBuildingJson(UnifiedBuildingData data) string Serializes data to JSON string

Floor Creation

MethodReturnsDescription
CreateFloor(BuildingController building) FloorController Adds an empty floor to a building
CreateFloor(FloorData data, BuildingController building) FloorController Creates a floor from data with all rooms/openings

Room & Opening Creation

MethodReturnsDescription
CreateRectangularRoom(float w, float l, Vector3 pos, FloorController, string name) RoomController Creates a simple rectangular room
CreateRoom(List<Vector3> points, FloorController, string name) RoomController Creates a room from arbitrary polygon points
CreateRoom(FloorSpaceItemData data, FloorController) RoomController Creates a room from serialized data
CreateDoor(FloorController, Vector3 pos, float w, float h) OpeningController Creates a door opening
CreateWindow(FloorController, Vector3 pos, float w, float h, float ypos) OpeningController Creates a window opening
CreateOutside(FloorSpaceItemData data, FloorController) OutsideController Creates an outdoor area
DeleteSpace(SpaceController, FloorController) void Removes a room/outside and disconnects from UI
DeleteOpening(OpeningController, FloorController) void Removes an opening and disconnects from UI

Complete Example

C#
// Example 1: Create a simple house programmatically
var settings = new BuildingConstructionSettings();
settings.ApplyDefaults();

BuildingController building = HeadlessBuildingAPI.Instance.CreateBuilding("My House", settings);
FloorController floor = HeadlessBuildingAPI.Instance.CreateFloor(building);

// Create a 6x8 meter room
RoomController room = HeadlessBuildingAPI.Instance.CreateRectangularRoom(
    width: 6f, length: 8f,
    floorCtrl: floor,
    position: Vector3.zero,
    roomName: "Living Room"
);

// Add a front door
OpeningController door = HeadlessBuildingAPI.Instance.CreateDoor(
    floor, worldPos: new Vector3(8f, 0, 0),
    w: 1.0f, h: 2.0f
);

// Add windows
HeadlessBuildingAPI.Instance.CreateWindow(
    floor, worldPos: new Vector3(-3f, 0, 0),
    windowWidth: 1.2f, windowHeight: 1.0f
);

// Build the 3D geometry
building.Build();
C#
// Example 2: Create building from data objects
var buildingData = new UnifiedBuildingData("v3");
buildingData.constructionSettings.ApplyDefaults();

var floor = new FloorData(null);

// Add room with normalized positions
var office = new FloorSpaceItemData(SpaceType.Room, "Office");
office.normalizedPositions = BuildingFactory.Instance.ConvertWorldPointsToNormalized(
    new List<Vector3> {
        new Vector3(-2, 0, -2), new Vector3(2, 0, -2),
        new Vector3(2, 0, 2),  new Vector3(-2, 0, 2)
    });
floor.spaces.Add(office);
buildingData.floors.Add(floor);

BuildingController building = HeadlessBuildingAPI.Instance.CreateBuilding("Office", buildingData);
building.Build();
C#
// Example 3: Create from JSON string
string json = File.ReadAllText("building.json");
BuildingController building = HeadlessBuildingAPI.Instance.CreateBuildingFromJson("Loaded", json);
building.Build();

// Optionally connect to UI
BuildingUIBridge.Instance?.ConnectBuildingToUI(building);

BuildingController

Root controller for a building. Manages floor collection, construction settings, and the build pipeline. Static currentBuilding tracks the active building.

Properties

PropertyTypeDefaultDescription
wallsHeightfloat3.0Height of walls in meters
doorsHeightfloat2.5Height of door openings
interiorWallThicknessfloat0.05Thickness of interior walls
exteriorWallThicknessfloat0.1Thickness of exterior walls
windowsThicknessfloat0.06Thickness of window frames
doorsThicknessfloat0.06Thickness of door frames
roofRoofConfigRoof type, thickness, and overhang

Key Methods

MethodDescription
Init()Initializes the floor list
Build()Builds all floors (or current floor if set)
GetData() → UnifiedBuildingDataExtracts complete building data for serialization
GetConstructionSettings()Returns construction settings as serializable data
SetConstructionSettings(settings)Applies settings from data object
GetAllFloors() → List<FloorController>Returns all floor controllers
GetCurrentFloor()Returns the currently active floor
SetCurrentFloor(FloorController)Sets which floor is active
AddFloorIfNew(FloorController)Adds a floor if its unique ID is new
static GetCurrentBuilding()Returns the global active building
static SetCurrentBuilding(building)Sets the global active building

IObjectDrawer Interface

Common interface implemented by both SpaceController and OpeningController. Defines the contract for drawable objects.

C#
public interface IObjectDrawer
{
    ControlPointsController Cpc { get; set; }
    Color DrawingColor { get; set; }

    void Init();                            // Initialize for drawing
    void Build();                           // Generate 3D geometry
    void SetDisplayed(bool displayed);     // Show/hide
    void SetData(FloorPlanItemData data);   // Apply data
    FloorPlanItemData GetData();           // Read data
    void OnSettingsChanged(FloorPlanItemData data);
}

SpaceController

Base class for room and outside controllers. Inherits BuildingComponentBase, implements IObjectDrawer. Manages control points, procedural space generation, and opening lists.

OpeningController

Controls doors, windows, and archways. Manages drawing, snapping to walls, and procedural mesh generation. Throttles rebuilds with configurable delayBetweenRebuilds.

FloorController

Manages all rooms and openings for a single floor. Runs the multi-pass build pipeline: collecting openings, building spaces twice (so wall geometry exists before opening holes are cut), building opening meshes, and finally building the exterior shell.

Key Methods

MethodReturnsDescription
Build()voidFull floor pipeline: collect → build spaces × 2 → build openings → exterior
GetAllSpaces()List<SpaceController>All room and outside controllers on this floor
GetAllOpenings()List<OpeningController>All door/window/archway controllers on this floor
GetFloorData()FloorDataExtracts serializable floor data for save/undo
SetFloorData(FloorData)voidApplies serialized data to reconstruct the floor
GetUniqueId()stringReturns the floor's stable GUID

Build Pipeline

FloorController.Build()
Collect openings
Build spaces (pass 1)
Snap openings to walls
Build spaces (pass 2)
Build opening meshes
Build exterior shell
💡
Spaces are built twice: the first pass establishes wall geometry so openings can snap to exact wall positions; the second pass cuts the wall holes where each opening was placed.

Event System

All inter-system communication uses System.Action delegates in Exoa.Events.HomeDesignerEvents. No Unity messaging — clean, type-safe event dispatch.

💡
Pattern: OnRequest* events are fired by UI to request actions. OnFile* events notify about persistence operations. Always unsubscribe in OnDisable() or OnDestroy().

File & Persistence Events

EventSignatureDescription
OnFileLoadedAction<FileType>Fired after a building data file or screenshot is loaded
OnFileCreatedAction<FileType>Fired after a new file is created
OnFileSavedAction<string, FileType>Fired after a file is saved (name, type)
OnFileChangedAction<string, FileType>Fired when active file changes
OnScreenShotSavedAction<string, MenuType>Fired after screenshot saved

Request Events (UI → Logic)

EventSignatureDescription
OnRequestClearAllAction<bool, bool, bool>Clear scene / floor UI / floor-plan UI
OnRequestRebuildActionRequest full rebuild of building geometry
OnRequestRepositionOpeningsActionReposition all openings (wall snapping)
OnRequestButtonActionAction<ButtonAction, bool>Toolbar button pressed (action, active state)
OnRequestFloorActionAction<FloorPlanAction, string>Floor-level operation (action, floorId)
OnRequestFloorPlanItemActionAction<FloorPlanItemAction, GameObject>Operation on a floor-plan item

State & UI Events

EventSignatureDescription
OnFloorChangedAction<FloorData, int>Displayed floor changed (data, index)
OnUndoRedoStateChangedActionUndo/redo stack changed; refresh UI buttons
OnDragEventAction<bool>Camera drag start (true) / end (false)
OnRenderForScreenshotAction<bool>Before (true) / after (false) screenshot render

Enums

ButtonAction

ToggleExteriorWalls, ToggleGizmos, Exit, NewProject, ToggleRoof, NewFloor, ShowExteriorWalls, ShowGizmos, ShowRoof, NewFromTemplate, SaveProject, ProjectWizard, CenterBuilding, MoveBuildingToggle, Undo, Redo

FloorPlanAction

Select, Add, Remove, Duplicate, Preview

FloorPlanItemAction

EditPoints, Split, Move, Remove, Paint

FileType / MenuType

FileType: BuildingDataFile, ScreenshotFile
MenuType: FloorPlanMenu, InteriorMenu, FloorsMenu

C#
// Subscribing to events
void OnEnable()
{
    HomeDesignerEvents.OnFileLoaded += OnFileLoaded;
    HomeDesignerEvents.OnRequestRebuild += OnRebuild;
    HomeDesignerEvents.OnFloorChanged += OnFloorChanged;
}

void OnDisable()
{
    HomeDesignerEvents.OnFileLoaded -= OnFileLoaded;
    HomeDesignerEvents.OnRequestRebuild -= OnRebuild;
    HomeDesignerEvents.OnFloorChanged -= OnFloorChanged;
}

// Firing events
HomeDesignerEvents.OnRequestButtonAction?.Invoke(ButtonAction.SaveProject, true);
HomeDesignerEvents.OnRequestFloorAction?.Invoke(FloorPlanAction.Add, floorId);

Procedural Generation

The procedural system generates all 3D geometry from 2D floor plan data. It uses ProceduralToolkit for mesh building, ClipperLib for polygon offsetting (wall thickness), and LibTessDotNet for tessellation.

ProceduralRoom

Generates complete room geometry from a polygon of control points.

Walls

Tessellated wall meshes with configurable thickness. Interior walls use interiorWallThickness, built via polygon offsetting with ClipperLib.

Floor & Ceiling

Tessellated flat surfaces from the room polygon. Floor at Y=0, ceiling at Y=wallsHeight. Material-assignable per room.

Collision Box

Box collider for furniture placement and raycasting. Auto-sized to room bounds.

Reflection Probe

Per-room reflection probe for realistic material reflections in the 3D view.

ProceduralOpening

Generates door, window, and archway meshes. Supports configurable dimensions, glass panels, handles, and window subdivisions.

Opening TypeFeatures
DoorFrame mesh, door panel, configurable width/height
WindowFrame, glass panel, subdivisions (H & V), Y-position offset
ArchwayOpen archway without door/glass

ProceduralExterior

Builds the exterior shell from room contours using polygon union operations. Creates the outer walls visible in Preview mode.

Roof Types

Flat Roof

RoofType = 0 — Simple flat slab with configurable thickness and overhang.

Hipped Roof

RoofType = 1 — All sides slope down from a central ridge using straight skeleton algorithm.

Gabled Roof

RoofType = 2 — Traditional two-slope roof with vertical gable ends.

Build Pipeline Detail

FloorController.
Build()
Collect all
openings
Build spaces
(pass 1)
Snap openings
to walls
Build spaces
(pass 2)
Build openings
meshes
Build exterior
shell

💡

Spaces are built twice: first pass establishes wall geometry for opening snapping, second pass cuts wall holes where openings are placed.

Serialization

ProjectSerializer (singleton) handles all JSON persistence via the included Newtonsoft.Json (custom IL2CPP-compatible build).

Important: Do not replace the included Newtonsoft.Json with NuGet version — it will break IL2CPP builds.

Data Model Hierarchy

Structure
UnifiedBuildingData
├── version: "v3"
├── constructionSettings: BuildingConstructionSettings
│   ├── wallsHeight, doorsHeight
│   ├── interiorWallThickness, exteriorWallThickness
│   ├── windowsThickness, doorsThickness
│   ├── roofType, roofThickness, roofOverhang
│   └── RoofConfig { type, thickness, overhang, color }
├── estheticSettings: BuildingEstheticSettings
│   └── exteriorWallMat, roofMat, texture settings
└── floors: List<FloorData>
    ├── uniqueId: string
    ├── spaces: List<FloorSpaceItemData>
    │   ├── type: SpaceType (Room | Outside | SingleWall)
    │   ├── name, uniqueId
    │   ├── normalizedPositions: List<Vector2>
    │   ├── roomSettings (wall/floor/ceiling textures)
    │   └── sceneObjects (furniture placements)
    └── openings: List<FloorOpeningItemData>
        ├── type: OpeningType (Door | Window | Archway)
        ├── name, uniqueId
        ├── width, height, ypos
        ├── normalizedPositions: List<Vector2>
        ├── directions: List<Vector3>
        ├── hasWindow, windowFrameSize
        └── windowSubDivH, windowSubDivV

Enums

EnumValues
SpaceTypeRoom = 0, Outside = 1, SingleWall = 2
OpeningTypeDoor = 0, Window = 1, Archway = 2
RoofTypeFlat = 0, Hipped = 1, Gabled = 2

Save / Load Flow

Save
:
BuildingController.GetData()
UnifiedBuildingData
JsonConvert.Serialize
JSON File
Load
:
JSON File
JsonConvert.Deserialize
UnifiedBuildingData
HeadlessBuildingAPI.CreateBuilding()

Undo/Redo

UndoRedoManager stores up to 50 JSON snapshots. Before each user action, TakeSnapshot() serializes the current building state. Undo/Redo destroys the current building and recreates from a snapshot.

File Locations

LocationPurpose
Application.persistentDataPathLocal project saves

JSON Example

JSON
{
  "version": "v3",
  "floors": [
    {
      "uniqueId": "e9eb0652-19b3-475a-9b47-7bce13274c22",
      "spaces": [
        {
          "type": 0,
          "name": "Office",
          "normalizedPositions": [
            { "x": 0.3, "y": 0.3 },
            { "x": 0.7, "y": 0.3 },
            { "x": 0.7, "y": 0.7 },
            { "x": 0.3, "y": 0.7 }
          ]
        }
      ],
      "openings": [
        {
          "type": 0,
          "width": 1.0,
          "height": 2.0,
          "name": "Office Door",
          "normalizedPositions": [{ "x": 0.5, "y": 0.3 }]
        }
      ]
    }
  ],
  "constructionSettings": {
    "wallsHeight": 2.5,
    "doorsHeight": 2.0,
    "interiorWallThickness": 0.05,
    "exteriorWallThickness": 0.1,
    "roofType": 2,
    "roofThickness": 0.1,
    "roofOverhang": 0.8
  }
}

Save / Load Walkthrough

The simplest way to trigger save and load is through the event system, which lets existing UI and any other subscribers react automatically:

C#
// ── Trigger save (fires OnFileSaved when complete) ──────────────────
HomeDesignerEvents.OnRequestButtonAction?.Invoke(
    HomeDesignerEvents.ButtonAction.SaveProject, true);

// ── Listen for save / load completion ───────────────────────────────
void OnEnable()
{
    HomeDesignerEvents.OnFileSaved  += OnSaved;
    HomeDesignerEvents.OnFileLoaded += OnLoaded;
}

void OnDisable()
{
    HomeDesignerEvents.OnFileSaved  -= OnSaved;
    HomeDesignerEvents.OnFileLoaded -= OnLoaded;
}

void OnSaved(string fileName, HomeDesignerEvents.FileType type)
    => Debug.Log($"Saved: {fileName}");

void OnLoaded(HomeDesignerEvents.FileType type)
    => Debug.Log("Project loaded");

// ── Direct API (bypasses event chain) ───────────────────────────────
ProjectSerializer.Instance.SaveProject("MyHouse");
ProjectSerializer.Instance.LoadProject("MyHouse");

// ── Enumerate saved files ────────────────────────────────────────────
string[] files = System.IO.Directory.GetFiles(
    Application.persistentDataPath, "*.json");
foreach (var f in files)
    Debug.Log(System.IO.Path.GetFileNameWithoutExtension(f));

Module System (Furniture)

Furniture modules and their categories are configured through the HomeDesignerSettings database. Open it via Tools > Exoa > Home Designer > Settings.

Module Data Model

C#
public class SceneObjectItemData
{
    public string uniqueId;      // Instance GUID
    public string objectId;      // Prefab identifier
    public Vector3 position;     // World-space position
    public Vector3 rotation;     // Euler rotation angles
    public Vector3 scale;        // Local scale
    public int variantIndex;     // Active variant index
}

Asset Locations

Wall materials, floor materials, and furniture module prefabs are no longer loaded from a Resources/ folder. All asset references are managed directly in the HomeDesignerSettings database (Tools > Exoa > Home Designer > Settings).

Sample Module Categories

Ready-to-use furniture prefabs in Samples/Modules/, each with a thumbnail:

CategoryContents
Appliances/Kitchen & utility appliances
BedRoom/Beds, wardrobes, bedroom furniture
DoorsWindows/Door, window, and archway modules
Kitchen/Cabinets, worktops, kitchen-specific items
Lights/Ceiling lights, floor lamps, wall fixtures
LivingRoom/Sofas, tables, living area furniture
Patio/Outdoor & patio elements

Creating a Custom Module

Any GameObject from the Project panel can be converted into a ready-to-use furniture module in a few steps.

1
Select the GameObject
Select a prefab or model in the Project panel
2
Run the Converter
Tools > Exoa > Home Designer > Convert Game Object to Module
3
Configure Collider
Resize the Box Collider to match the object's full shape
4
Configure Component
Set category and thumbnail in ModuleController

The converter creates a new prefab containing your object, a Box Collider (used for placement validation and snapping), and a ModuleController component. Once the prefab is configured, register it in the HomeDesignerSettings database to make it available at runtime.

💡
Resize the Box Collider to cover the entire footprint of the object — this drives both placement collision detection and the snapping behaviour against walls and other modules.

Interior Design System

The interior design system handles furniture and decoration placement in 3D space. It activates in the Furnish app state and supports both mouse and touch-driven module manipulation with ghost preview, wall detection, and variant switching.

Key Classes

InteriorDesigner
  • Orchestrates module placement & selection
  • Ghost (preview) object shown before confirming placement
  • Wall & joint detection for smart positioning
  • Long-press drag interaction
  • Raises OnRequestButtonAction events for toolbar state
ModuleController
  • Controls an individual placed furniture piece
  • Handles selection, drag, rotate, and scale
  • Arrow key rotation (15° steps) and scale adjustment
  • Placement validation (collision, floor bounds)
  • Serializes state as SceneObjectItemData
ModuleVariants
  • Stores alternative visual representations per module
  • Variant index persisted in SceneObjectItemData.variantIndex
  • Switched at runtime without re-instantiation
ModuleControllerEditor
  • Custom Unity Editor inspector for ModuleController
  • Warns in the Inspector when a settings entry has a missing prefab reference
  • Located in Scripts/Editor/

Placement Layers

Modules raycast against the following Unity layers to determine valid placement surfaces:

LayerSurface
InteriorFloorInterior room floor meshes
ExteriorFloorExterior / outdoor floor surfaces (terraces, patios, gardens)
💡
Assign the ExteriorFloor layer to any exterior ground geometry to allow module placement on outdoor surfaces. Both layers are checked simultaneously during placement raycasts.

Serialized Module Data

Placed modules are stored as SceneObjectItemData entries inside FloorSpaceItemData.sceneObjects:

C#
public class SceneObjectItemData
{
    public string uniqueId;      // Instance GUID
    public string objectId;      // Prefab identifier
    public Vector3 position;     // World-space position
    public Vector3 rotation;     // Euler rotation angles
    public Vector3 scale;        // Local scale
    public int variantIndex;     // Active variant index
}

Material System

Materials can be applied independently to interior room surfaces (per-wall, floor, ceiling), exterior building walls, and the roof. All material controllers are render-pipeline aware and work with Built-in RP, URP, and HDRP.

Material Controllers

SpaceMaterialController
  • Applies materials to an individual room's surfaces
  • Per-room wall, floor, and ceiling material slots
  • Reads from RoomSetting inside FloorSpaceItemData
  • Triggered in the Paint app state
ExteriorMaterialController
  • Controls exterior wall and roof surface materials
  • Reads from BuildingEstheticSettings
  • Applied to ProceduralExterior and roof geometry

Material Asset References

Wall and floor material assets are registered in the HomeDesignerSettings database. Open it via Tools > Exoa > Home Designer > Settings to add, remove, or reorder available materials.

RoomSetting — Per-Room Textures

C#
// RoomSetting is stored inside FloorSpaceItemData
public class RoomSetting
{
    public string wallMat;     // Wall material resource name
    public string floorMat;    // Floor material resource name
    public string ceilingMat;  // Ceiling material resource name
}

Render Pipeline Switching

Use Tools > Exoa > Render Pipeline Switcher to batch-convert all project materials between Built-in RP, URP, and HDRP. At runtime, MaterialExtensions handles pipeline-aware material property lookups (e.g., _BaseColor vs _Color).

Preview Mode

PreviewModeManager controls the multi-floor 3D preview where all floors of a building are displayed simultaneously, stacked vertically so the entire building is visible at once.

How It Works

When entering the Preview state, each FloorController is repositioned at Y = floorIndex × wallsHeight. The exterior shell and roof are rebuilt to span the full stacked height. Returning to another state restores each floor to Y = 0 and rebuilds normally.

Enter Preview state
PreviewModeManager
Stack floors at Y = i × wallsHeight
Rebuild exterior & roof
Full 3D building view

Transition Events

Entering/leaving preview mode fires HomeDesignerEvents.OnRequestRebuild and triggers camera refocus via CameraEvents. UI panels are hidden (only preview controls remain visible) via CanvasGroupsFader.

State Table

Preview OnPreview Off
All floors stacked at Y intervalsAll floors at Y = 0
Exterior shell spans full heightPer-floor exterior built independently
Furniture visible on all floorsOnly current floor furniture visible in edit views

UI System

Event-driven UI that subscribes to HomeDesignerEvents and fires OnRequest* events. All panel transitions managed by CanvasGroupsFader.

ClassPurposeActive In State
UIFloorsMenuFloor list — add, remove, duplicate, selectFloors
UIFloorPlanMenuRoom and opening list for current floorFloorPlan
UIFurnishMenuFurniture category browser & placementFurnish
UIBuildingSettingsWall height, thickness, roof controlsFloorPlan, Paint

Popup Types

BaseFloatingPopup

Draggable popup that follows a target object. Used for contextual menus attached to rooms/openings.

BaseStaticPopup

Modal overlay popup. Used for alerts, settings, and forms.

AlertPopup

Simple alert dialog with title, message, and confirmation button.

MaterialPopup

Material picker for walls, floors, and ceilings. Loads from Resources.

AddFloorPlanItemPopup

Popup for adding new rooms or openings with type selection.

ControlPointInfoPopup

Context menu for control points — edit coordinates, delete points.

UI Item Hierarchy

Hierarchy
UIBaseItem
├── UISpaceBaseItem
│   └── UIRoomItem        // Room list entry
├── UIOpeningBaseItem
│   └── UIOpeningItem     // Door/window list entry
└── UIFloorItem           // Floor list entry

BuildingUIBridge

Singleton that connects scene objects to UI list items. Key methods:

MethodDescription
ConnectBuildingToUI(BuildingController)Connect entire building hierarchy to UI
ConnectFloorToUI(FloorController)Connect a floor to the floor list
ConnectToUI(IObjectDrawer)Connect a room/opening to the item list
DisconnectFromUI(IObjectDrawer)Remove a room/opening from the UI

Custom UI Integration

The built-in UI is entirely optional. You can replace it, extend it, or drive the plugin from a completely different interface — the core logic communicates only through HomeDesignerEvents static delegates.

Wiring a Custom Button

To trigger any action from your own UI, fire the appropriate OnRequest* event:

C#
// Save project from a custom Save button
public void OnSaveClicked()
{
    HomeDesignerEvents.OnRequestButtonAction?.Invoke(
        HomeDesignerEvents.ButtonAction.SaveProject, true);
}

// Switch to Furnish mode from a custom tab
public void OnFurnishTabClicked()
{
    AppController.Instance.State = AppController.States.Furnish;
}

// Add a new floor from a custom button
public void OnAddFloorClicked()
{
    HomeDesignerEvents.OnRequestFloorAction?.Invoke(
        HomeDesignerEvents.FloorPlanAction.Add, null);
}

Reacting to State Changes

Subscribe to AppController.OnAppStateChange to show/hide your own panels when the app state changes:

C#
void OnEnable()
{
    AppController.OnAppStateChange += OnStateChanged;
}

void OnDisable()
{
    AppController.OnAppStateChange -= OnStateChanged;
}

void OnStateChanged(AppController.States state)
{
    myFloorPanel.SetActive(state == AppController.States.Floors);
    myFurnishPanel.SetActive(state == AppController.States.Furnish);
}

Replacing the UI Entirely

To run with no built-in UI at all:

  1. Remove the UI Canvas prefab from your scene.
  2. Use HeadlessBuildingAPI to create and modify buildings from code.
  3. Subscribe directly to HomeDesignerEvents delegates to react to persistence and state changes.
  4. Call AppController.Instance.State = ... to drive the state machine from your own logic.
💡
You do not need to call BuildingUIBridge.ConnectBuildingToUI() when running without the built-in UI — that method only wires scene objects to the built-in list panels.

Camera System

TouchCameraLite provides orbit, pan, zoom, and focus controls. Supports both mouse and touch input for desktop and mobile platforms.

Key Features

Orbit & Pan

Smooth orbit around a focal point with configurable damping. Pan via middle-mouse or two-finger drag.

Zoom

Mouse scroll or pinch-to-zoom with min/max distance limits and smooth interpolation.

Focus

Auto-center camera on selected objects with smooth transition. Triggered by F key or CameraEvents.

Perspective Switch

Toggle between perspective and orthographic views. Top-down view for floor plan editing, 3D for interior design.

CameraEvents

Secondary event source for camera-specific operations: focus on objects, perspective switching, and camera state changes.

Extension Methods

A rich set of C# extension methods in Exoa.Extensions namespace.

FileExtendsKey Methods
TransformExtensionsTransformDestroyUniversal, find helpers
RendererExtensionsRendererBounds calculations, material helpers
MaterialExtensionsMaterialPipeline-aware material operations
ComponentExtensionsComponentGetOrAddComponent, hierarchy queries
ColliderExtensionsColliderCollider sizing and bounds
ListExtensionsList<T>Shuffle, safe access
StringExtensionsstringSanitization, formatting
RectTransformExtensionsRectTransformUI layout helpers
AnimatorExtensionsAnimatorAnimation state queries

Troubleshooting

Icons Not Rendering

Ensure only one TMP Settings file exists in the project with the Font Awesome fallback font configured.

Loading / Saving Errors

You must use the included Newtonsoft.Json version. The NuGet version is not IL2CPP compatible and will cause runtime crashes.

HDRP Grid Not Visible

Adjust emissive intensity on grid material based on your directional light's emission settings.

Render Pipeline Setup

Use Tools > Exoa > Render Pipeline Switcher to convert all materials between Built-in, URP, and HDRP.

Input System Not Active

If keyboard shortcuts or touch input do nothing, verify the Input System package is installed and that Active Input Handling is set to Input System Package (New) or Both in Project Settings > Player. Restart the editor after changing this setting.

HomeDesignerSettings Asset Missing

If the plugin throws a NullReferenceException on startup, the HomeDesignerSettings ScriptableObject may be missing from the Resources/ folder. Re-import the package or create a new one via Tools > Exoa > Home Designer > Settings.

Module Not Appearing in the Panel

After creating a custom module, make sure it is registered in the HomeDesignerSettings database. The furniture panel only lists modules that appear in that database — adding the prefab to the project folder alone is not enough.

Domain Reload / Hot Reload Issues

Static events in HomeDesignerEvents are not cleared on domain reload. If you see duplicate event handlers after entering Play Mode a second time, ensure all subscribers unsubscribe in OnDisable() or OnDestroy().

GLB Export Button Missing

The GLB export option only appears in HomeDesignerSettings when the UNIGLTF scripting define symbol is present. See the GLB Export Setup section for install steps.

FAQ

Can I use Home Designer without the built-in UI?

Yes. HeadlessBuildingAPI creates and manipulates buildings entirely from code with no UI dependencies. Load the Demo_API scene for a working example, or instantiate HeadlessBuildingAPI in your own scene. The UI is optional — connect it later via BuildingUIBridge.Instance.ConnectBuildingToUI(building) if needed.

Can I have multiple buildings in one scene?

The system tracks one active building via BuildingController.currentBuilding. You can instantiate multiple BuildingController prefabs, but only one is "active" at a time for editor operations. Switching the active building is done with BuildingController.SetCurrentBuilding(building).

Does it work on WebGL?

The core procedural and serialization systems are WebGL-compatible. GLB export is not available on WebGL (filesystem access is restricted). Test thoroughly — some Application.persistentDataPath operations behave differently in a browser sandbox.

Can I customise keyboard shortcuts?

Yes — open the HomeDesignerInputActions asset in the Input Action editor and remap any binding. No code changes required. Camera shortcuts (F, Space, scroll) live in the same asset under the Camera action map.

How do I add a new app state?

Add a value to the AppController.States enum, then handle the new state in any subscriber of AppController.OnAppStateChange. The state machine itself has no hard-coded logic per state — all behaviour is driven by event subscribers.

Is the license check included in builds?

No. The license system is editor-only (#if UNITY_EDITOR). Builds contain no license enforcement — the DLLs that perform the check are editor assemblies and are stripped at build time.

Can I save and load multiple projects?

Yes. Projects are saved as individual JSON files (and optionally GLB files) under Application.persistentDataPath. Each file is identified by its project name. Call ProjectSerializer.Instance.SaveProject(name) and ProjectSerializer.Instance.LoadProject(name) to manage them, and enumerate the directory to show a file list in your UI.

Upgrading

When updating to a new version of Home Designer, follow these steps to avoid conflicts:

  1. Back up your project (or commit to version control) before importing the new package.
  2. Delete the old package folder (Assets/Exoa/HomeDesigner/) before importing the new .unitypackage — stale scripts can cause compile errors if files are renamed or removed.
  3. Re-run the Render Pipeline Switcher after import if new materials were added (Tools > Exoa > Render Pipeline Switcher).
  4. Regenerate the Input Actions asset if new shortcuts were added — run Tools > Exoa > Create Input Actions Asset to overwrite the old one.
  5. Check HomeDesignerSettings for any new fields that need to be configured after the upgrade.
Saved project files are forward-compatible — the JSON format is versioned ("version": "v3"). Existing saves load correctly in newer versions. Always verify with a test load after upgrading.

Breaking Changes to Watch For

AreaWhat to check
Renamed classesCheck the release notes for any class renames (e.g. FloorPlanSerializerProjectSerializer) and update your own code accordingly.
Removed scripting symbolsOld symbols (FLOORMAP_MODULE, INTERIOR_MODULE, FBX_EXPORTER) are no longer used — remove them from Player Settings to avoid confusion.
Input SystemIf the HomeDesignerInputActions asset was customised, merge your bindings manually after regenerating.

Third-Party Dependencies

LibraryPurposeNotes
Newtonsoft.Json JSON serialization/deserialization Custom IL2CPP-compatible build — do NOT replace
ProceduralToolkit Mesh construction (MeshDraft abstraction) Included in Packages/Toolkit/
ClipperLib Polygon offsetting for wall thickness Bundled with ProceduralToolkit
LibTessDotNet Polygon tessellation Bundled with ProceduralToolkit
Outline Object selection outline / highlight effect Included in Packages/Outline/ — assembly: Exoa.Effects.Scripts
Unity GLB Exporter Export buildings as GLB files for cloud upload Install via Package Manager — enable with UNIGLTF symbol
TextMeshPro UI text rendering With Font Awesome fallback for icons

Common Recipes

Copy-pasteable snippets for the most frequent tasks.

Create a rectangular room from code

C#
var settings = new BuildingConstructionSettings();
settings.ApplyDefaults();

BuildingController building = HeadlessBuildingAPI.Instance.CreateBuilding("House", settings);
FloorController    floor    = HeadlessBuildingAPI.Instance.CreateFloor(building);
RoomController     room     = HeadlessBuildingAPI.Instance.CreateRectangularRoom(
    width: 5f, length: 7f, position: Vector3.zero, floorCtrl: floor, roomName: "Living Room");

building.Build();

Trigger a full rebuild

C#
HomeDesignerEvents.OnRequestRebuild?.Invoke();

Switch app state

C#
AppController.Instance.State = AppController.States.FloorPlan;  // enter draw mode
AppController.Instance.State = AppController.States.Preview;    // enter 3D preview

Clear the current scene

C#
// args: clearScene, clearFloorUI, clearFloorPlanUI
HomeDesignerEvents.OnRequestClearAll?.Invoke(true, true, true);

React to floor selection changes

C#
void OnEnable()  => HomeDesignerEvents.OnFloorChanged += OnFloorChanged;
void OnDisable() => HomeDesignerEvents.OnFloorChanged -= OnFloorChanged;

void OnFloorChanged(FloorData data, int index)
    => Debug.Log($"Now editing floor {index}");

Load a building from a JSON string

C#
string json = File.ReadAllText(path);
BuildingController building = HeadlessBuildingAPI.Instance.CreateBuildingFromJson("Loaded", json);
building.Build();
BuildingUIBridge.Instance?.ConnectBuildingToUI(building);

Focus camera on the current building

C#
CameraEvents.OnRequestObjectFocus?.Invoke(
    BuildingController.GetCurrentBuilding().gameObject, true);

Assembly Definitions

The project uses assembly definitions to isolate libraries and reduce compile times:

AssemblyLocationReferences
Exoa.HomeDesigner.ScriptsScripts/Common assemblies, ProceduralToolkit, TouchCameraPro, Effects
Exoa.Designer.EditorEditor/Exoa.HomeDesigner.Scripts
Exoa.HomeDesigner.Tests.EditorTests/Editor/Exoa.HomeDesigner.Scripts
Exoa.Common.Scripts.ExtensionsScripts/Extensions/Maths, Touch
Exoa.Common.Scripts.MathsScripts/Maths/
Exoa.Common.Scripts.TouchScripts/Inputs/Touch/
ProceduralToolkitPackages/Toolkit/Runtime/ClipperLib, LibTessDotNet
ProceduralToolkit.ClipperLibPackages/Toolkit/Runtime/ClipperLib/
ProceduralToolkit.LibTessDotNetPackages/Toolkit/Runtime/LibTessDotNet/
ProceduralToolkit.EditorPackages/Toolkit/Editor/ProceduralToolkit
Exoa.TouchCameraPro.ScriptsPackages/TouchCameraLite/Scripts/
Exoa.TouchCameraPro.EditorPackages/TouchCameraLite/Editor/Exoa.TouchCameraPro.Scripts
Exoa.Effects.ScriptsPackages/Outline/
Exoa.Effects.Scripts.EditorPackages/Outline/Scripts/Editor/Exoa.Effects.Scripts

Service Locator

ServiceLocator is a lightweight dependency injection container that decouples systems from direct singleton references. Services are registered once (typically in Awake) and resolved wherever needed.

C#
// Register a service (e.g. in Awake)
ServiceLocator.Register<BuildingController>(building);

// Resolve anywhere — no direct static reference needed
var building = ServiceLocator.Get<BuildingController>();
building.Build();

// Check registration before use
if (ServiceLocator.Has<BuildingController>())
    ServiceLocator.Get<BuildingController>().Build();

// Unregister on scene unload
ServiceLocator.Clear();
💡
Prefer ServiceLocator over direct singleton access when writing new systems — it keeps code testable and loosely coupled without requiring scene-level MonoBehaviour wiring.

BuildingFactory

BuildingFactory (singleton) provides factory methods for instantiating building component prefabs and converting between world-space coordinates and the normalized [0,1] range used in data models.

Key Methods

MethodDescription
ConvertWorldPointsToNormalized(List<Vector3>) Converts world-space polygon points to normalized Vector2 list for storage in FloorSpaceItemData.normalizedPositions
ConvertNormalizedPointsToWorld(List<Vector2>) Converts stored normalized positions back to world space for drawing and mesh generation
InstantiateRoom(prefab, parent) Creates a room GameObject as child of a FloorController
InstantiateOpening(prefab, parent) Creates an opening (door/window/archway) GameObject as child of a FloorController
💡
Normalized positions make data independent of the grid scale. When you load a floor plan into a scene with a different grid size, positions are re-scaled correctly. Always use BuildingFactory.ConvertWorldPointsToNormalized() before storing user-drawn points.