# Render engines

AsukaDesigner uses one engine map for every rendering backend. Fabric is the default engine; Babylon is loaded only for views whose `engine` is `'3d'`.

The production entry injects both standard lazy boundaries automatically. You
only need to provide `loader` when replacing a renderer or when using a
lower-level build.

```ts
import {
  createAsukaDesigner,
  loadAsuka3DEngineAddon,
  loadAsukaFabricEngineAddon,
} from './dist/product/asukadesigner.min.js';

const designer = createAsukaDesigner('#app', {
  engine: 'fabric',
  engines: {
    fabric: {
      loader: loadAsukaFabricEngineAddon,
    },
    '3d': {
      loader: loadAsuka3DEngineAddon,
      history: { maxSnapshots: 8 },
      camera: {
        minBetaDegrees: 5,
        maxBetaDegrees: 88,
        fitOnAdd: false,
        fitViewRadiusRatio: 1.35,
      },
      products: {
        centerGroup: true,
        centerOnAdd: true,
        centerOnTransform: true,
        centerInView: true,
        lockCameraTarget: true,
        centerDuringFocus: true,
      },
      scene: {
        products: [],
      },
    },
  },
  views: [
    { id: 'front', name: 'Front', engine: 'fabric', width: 1200, height: 1600 },
    {
      id: 'preview-3d',
      name: '3D preview',
      engine: '3d',
      width: 1200,
      height: 900,
      scene3d: {
        products: [],
        camera: {
          position: [0, 1.4, -4],
          target: [0, 0.7, 0],
          fov: 0.8,
        },
      },
    },
  ],
});
```

There is no second 3D configuration entry, model shortcut or engine alias. Use only:

- `engines.fabric`
- `engines['3d']`
- `view.scene3d`

## Canonical 3D scene

A 3D scene is an Asuka state object, not raw Babylon serialization. It always uses `products` as its product collection.

```ts
const scene = {
  products: [
    {
      id: 'shirt-1',
      catalogId: 'basic-shirt',
      name: 'Basic shirt',
      model: { url: '/models/shirt.glb', format: 'glb' },
      parts: [],
      variationId: 'white',
      variationName: 'White',
      partVariationIds: {},
      transform: {
        position: { x: 0, y: 0, z: 0 },
        rotation: { x: 0, y: 0, z: 0 },
        scale: { x: 1, y: 1, z: 1 },
      },
    },
  ],
  selectedProductId: 'shirt-1',
  selectedPartId: null,
  camera: {
    position: [0, 1.4, -4],
    target: [0, 0.7, 0],
    fov: 0.8,
  },
  materials: {},
  decorations: [],
  printAreas: [],
  activePrintAreaId: null,
  selectedObjectId: null,
  selectedMaterialId: null,
};
```

The runtime intentionally rejects removed singular fields and aliases. It does not migrate them. A model belongs to `products[n].model`; a view id belongs to the document/view map and is never stored inside the scene itself.

Use the public helpers when an integration creates scene state directly:

```ts
import {
  assertCanonical3DSceneState,
  create3DSceneState,
} from 'asuka-designer';

const scene = create3DSceneState({
  products: [],
  camera: { position: [0, 1.4, -4], target: [0, 0.7, 0] },
});

assertCanonical3DSceneState(scene);
```

Exported 3D view states are stored by view id in `document.scenes3d`.

## Product catalog entries

Products remain catalog Elements with `type: 'product-3d'`. Their source model is defined only in `product3d.model`.

```ts
const shirt = {
  id: 'basic-shirt',
  familyId: 'basic-shirt',
  name: 'Basic shirt',
  type: 'product-3d',
  product3d: {
    model: { url: '/models/shirt.glb', format: 'glb' },
    parts: [],
    printAreas: [],
  },
  variations: [
    {
      id: 'white',
      name: 'White',
      active: true,
      product3d: {
        materialTarget: 'product',
        materials: {
          product: {
            color: '#ffffff',
            roughness: 0.75,
            metallic: 0,
          },
        },
      },
    },
  ],
};
```

Applying this catalog entry creates a new object in `scene.products`. The placed product receives its own stable `id`; `catalogId` points back to the catalog entry. Variations and transforms are therefore isolated per placed product.

## Product parts and variations

A model can expose logical parts from its GLB/glTF meshes. The engine stores them in `products[n].parts`. Part-specific selections are stored in `products[n].partVariationIds`.

The Elements and Variations modules may focus the camera on a part. Direct canvas selection does not automatically change the camera.

```ts
const engine = designer.util.engine.getCurrent();
const products = engine.listProducts?.() ?? [];
const product = engine.getProduct?.('shirt-1');
const parts = engine.listParts?.() ?? [];

engine.selectProduct?.(product?.id ?? products[0]?.id ?? null);

// Activate a product for parts/variations without selecting it for transforms.
engine.selectProduct?.(product?.id ?? null, { selectObject: false });
engine.selectPart?.(parts[0]?.id ?? null);
await engine.applyPartVariation?.(parts[0].id, 'red', {
  productId: 'shirt-1',
  focus: true,
});
```

## Print areas

A 3D print area uses one of two explicit modes:

- `texture`: objects are composed in UV/texture space. This is the preferred print-output path.
- `surface`: objects are projected on the visible product surface.

```ts
const area = {
  id: 'front-chest',
  label: 'Front chest',
  targetMeshId: 'shirt-front',
  mode: 'texture',
  textureSlot: 'diffuseTexture',
  uvBounds: { x: 0, y: 0, width: 1, height: 1 },
  canvasSize: { width: 2048, height: 2048 },
  objects: [],
  visible: true,
  locked: false,
};
```

The active area is stored in `scene.activePrintAreaId`. Text, image and design modules place their next object in that area. Texture areas always use the texture compositor; they never silently fall back to surface projection.

## Engine capabilities

Modules must use the engine facade rather than importing Fabric or Babylon classes.

```ts
const engine = designer.util.engine.getCurrent();
const capabilities = designer.util.engine.getCapabilities();
const graph = designer.util.engine.getSceneGraph();
```

Shared modules can declare their supported engines and required capabilities:

```ts
registerModule({
  id: 'layers',
  label: 'Layers',
  supportedEngines: ['fabric', '3d'],
  requiredCapabilities: ['objects'],
  render: LayersModule,
});
```

The main product bundle must remain engine-neutral. `build:product` emits
`asukadesigner.min.js`, the independent Fabric and Babylon renderer files, the
shared stylesheet, and optional named licensed add-ons. Run the audits after
engine, add-on, or state-contract changes:

```bash
npm run audit:engines
npm run audit:3d-contract
npm run check:bundle
```
