3D Scene
Aircada separates visual scene composition from logic implementation. All 3D objects, materials, and world settings are defined within the Studio environment.
The Aircada CLI processes this scene data to generate src/registry/scene.registry, a comprehensive bridge that allows Operators to reference scene objects with full TypeScript safety.
To ensure clean development, the registry is architected into three layers: Raw IDs, Operator Refs (Facades), and Data Registries.
Programmatic Scene Creation & Entity Management (air.scene)#
In addition to static scene binding via @SceneObjectProperty(), the Aircada SDK exposes this.air.scene for imperative, programmatic entity creation and lifecycle management at runtime.
Standard CRUD Methods on this.air.scene
- create(typeOrConfig, properties): Asynchronously creates and mounts an active 3D entity. - clone(unitIdOrName, options): Clones an existing scene entity with optional name or transform overrides. - destroy(unitIdOrName): Destroys the entity and disposes of all underlying Three.js meshes and materials. - get(unitIdOrName): Retrieves a strongly-typed UnitFacade / ModelFacade for an entity by ID or display name. - getAll(): Retrieves all active scene entity facades. - getByType(type): Retrieves all entities matching a specific PrefabAssetType (e.g. MODEL, SHAPE, LIGHT). - show(unitIdOrName) / hide(unitIdOrName): Toggles visibility and hierarchy rendering.
Supported Entity Types
this.air.scene.create() supports the following entity types: - MODEL (or OBJECT_PART): 3D GLTF / GLB meshes, materials, and bounding scale controllers. - SHAPE: 3D primitives (CUBE, SPHERE, CYLINDER, PLANE, CONE, TORUS). - LIGHT: Directional, Point, Ambient, Spot, and Area light sources. - TEXT: 3D extruded vector text geometry. - CAMERA: Viewports and render cameras. - PARTICLES: GPU particle emitters and simulation fields.
Synchronous Media Item Resolution & 3D Model Spawning
When spawning 3D models from uploaded files or dropzones (e.g. AirMediaDropzone), upload blobs are converted to temporary media items via mediaManager.makeTemporaryMediaItemFromBlob(file). The engine synchronously reserves the local media item handle in memory, allowing you to immediately pass mediaItemId to this.air.scene.create() without timing race conditions.
import { System, Context, Input, AircadaContext } from "@aircada/spec";
@System({
name: "Model Spawner System",
type: "model_spawner_system"
})
export class ModelSpawnerSystem {
@Context()
air!: AircadaContext;
@Input({ onEvent: "SPAWN_CUSTOM_MODEL" })
async spawnCustomModel(payload: {
name: string;
mediaItemId: string;
materialColor?: string;
metalness?: number;
scale?: number;
}) {
const { name, mediaItemId, materialColor = "#ffffff", metalness = 0, scale = 1 } = payload;
// Imperative 3D model creation via air.scene.create
const entity = await this.air.scene.create("MODEL", {
name,
mediaItemId,
color: materialColor,
materialColor,
metalness,
scale: { x: scale, y: scale, z: scale }
});
console.log("Spawned entity facade:", entity);
return entity;
}
}Ephemeral Runtime Entities & Project Isolation (_isTemporary)#
When entities are spawned imperatively via this.air.scene.create(), they are flagged with _isTemporary: true by default.
Project Save Isolation Guarantee
Ephemeral runtime entities are fully active in the live WebGL scene graph, physics engines, and raycasting systems, but they are strictly excluded when saving Studio projects (projectData.units). This ensures that procedural or user-uploaded session items do not pollute permanent project configurations.
Plugin Lifecycle & Auto-Disposers
By default, this.air.scene.create() registers an automatic disposer (props.autoDispose !== false) with the active plugin adapter. When an operator or system unmounts, or when hot module reloading (HMR) triggers a reload, all ephemeral entities spawned during that session are automatically destroyed and their GPU memory cleaned up.
import { System, Context, Input, AircadaContext } from "@aircada/spec";
@System({
name: "Temporary Entity Demo",
type: "temporary_entity_demo"
})
export class TemporaryEntityDemo {
@Context()
air!: AircadaContext;
@Input({ onEvent: "CREATE_SESSION_SHAPE" })
async createSessionShape() {
// Creates an ephemeral runtime entity that is auto-disposed on system unload
const shape = await this.air.scene.create("SHAPE", {
name: "Session_Cube",
shapeType: "CUBE",
position: [0, 1, 0],
color: "#4f46e5",
autoDispose: true // Default: auto-cleaned on operator/system unmount
});
return shape;
}
}Layer 3: Data Registries (The Source of Truth)#
When to use:
1. When you need context about what exists in the scene (e.g., light positions).
2. When identifying object relationships (e.g., shared material IDs).
Agent Heuristic: Treat this layer as Read-Only Context. Never attempt to mutate these registries at runtime.
Decorator Behavior: @SceneObjectProperty#
The @SceneObjectProperty() decorator tells the Aircada engine that a property expects a reference to another 3D object in the scene.
Studio Integration
Properties using this decorator are automatically exposed in the Studio UI as selection buttons on the Operator's configuration panel.
Nullable Initialization
You can safely initialize these properties with null or leave them blank. This allows designers in the Studio to pick the target object visually.
Automatic Hydration
At runtime, the engine injects the hydrated object (e.g., a ModelObject) into the property before the @Setup() method is called.
Layer 2: Operator Refs (The Magic Facades)#
This layer wraps raw IDs in a sceneObjectRef<T>(id) facade. It tells the compiler the string is a functional 3D object.
When to use: Specifically for assigning static default values to properties decorated with @SceneObjectProperty().
Agent Heuristic: NEVER attempt to read properties (like .transform) directly off SceneObjects.KEY inside logic blocks. The engine only hydrates these after assignment to a decorated property.
@SceneObjectProperty()
defaultShoe: ModelObject = SceneObjects.UPLOADED_OBJECT_1;Example: SceneObjectProperty in an Operator#
When an Operator needs to declare a relationship with another 3D object in the scene, use @SceneObjectProperty(). You should initialize it using the Layer 2 Operator Refs (e.g., SceneObjects) imported from the registry.
import { Setup, Operator, SceneObjectProperty, Context, ModelObject, AircadaContext } from "@aircada/spec";
import { SceneObjects } from "../registry/scene.registry";
@Operator({
name: "Focus Controller",
type: "FOCUS_CONTROLLER"
})
export class FocusController {
@Context()
air!: AircadaContext;
// 1. Declare the property and strongly type the expected hydrated object (e.g., ModelObject)
// 2. Initialize using the Layer 2 Operator Ref.
@SceneObjectProperty()
focusTarget: ModelObject = SceneObjects.UPLOADED_OBJECT_1;
@Setup()
setup() {
// Hydration happens automatically. The reference tells the engine which
// 3D object this operator targets without needing imperative lookup code.
}
}Layer 1: Raw IDs (The Master Indexes)#
When to use:
1. When an external system (network/dataset) provides a string ID.
2. When looking up an object's initial state in the Data Registry (Layer 3).
3. When logging or debugging sync issues.
Agent Heuristic: If an API specifically asks for an id: string, use this layer.
Studio Configuration & CLI Hydration#
In Aircada, the Studio acts as the primary environment for scene composition. Developers place objects, configure lighting, and assign materials visually.
When you run a sync via the CLI, the system generates or updates the project-registry.ts file. This file contains a snapshot of all static IDs and registries.
This registry is critical for Operators to 'hook' into the scene without needing to perform slow or error-prone string-based lookups at runtime.






