# Programmatic Scene Building (`DiscoVR` class)

For experiences that are easier to build in JavaScript than to describe with `<discovr>` tag attributes and HTML markup, DiscoVR Engine exposes a `DiscoVR` class as `window.DiscoVR`. It's a thin, generic layer for creating A-Frame entities from code — it doesn't duplicate the `<discovr>` tag's config-driven features (environment/lights presets, player rig, teleport, networking; see the [Configuration Reference](configuration-reference.md) for those).

`window.DiscoVR` becomes available as soon as `discovr-core.js` has run, so any `<script>` loaded after it can use it.

## Standalone usage

Without a `<discovr>` tag, `DiscoVR` finds or creates its own `<a-scene>`, so this is all you need:

```html
<script type="module" src="https://core.discovr-engine.eu/discovr-core.js"></script>
<script type="module">
    const discovr = new window.DiscoVR();
    discovr.addBox({ color: "red", position: "0 1 -3" }, "myBox");
</script>
```

## Using it alongside a `<discovr>`-configured scene

`DiscoVR` looks for an existing `<a-scene>` and reuses it if found, instead of creating a second one — but the `<discovr>` tag's own scene is built asynchronously during boot, so your script needs to wait until it exists before instantiating `DiscoVR`. The safe pattern is to wait for the scene's `loaded` event:

```js
function whenSceneReady(callback) {
    var scene = document.querySelector('a-scene');
    if (scene) {
        if (scene.hasLoaded) callback();
        else scene.addEventListener('loaded', callback);
    } else {
        setTimeout(() => whenSceneReady(callback), 100);
    }
}

whenSceneReady(() => {
    var discovr = new window.DiscoVR();
    discovr.addBox({ color: "blue" }, "myBox");
});
```

Instantiating `DiscoVR` before the `<discovr>` tag's scene exists will create a second, unrelated `<a-scene>` instead of adding to it — that's the one thing to avoid.

## API

### `new DiscoVR()`

Finds the page's `<a-scene>`, or creates and appends one to `<body>` if none exists yet.

### `getScene()`

Returns the `<a-scene>` element this instance builds into.

### `getAssets()`

Finds or creates the `<a-assets>` node inside the scene, and returns it. Called automatically by `addAsset()`.

### `addAsset(id, src)`

Registers an asset (model, image, audio, ...) under `<a-assets>` so it can be referenced elsewhere by `"#id"` — most commonly by `addModel()`.

| Param | Type | Description |
|---|---|---|
| `id` | `string` | The asset's id. |
| `src` | `string` | Path or URL to the asset file. |

Returns the created element: an `<a-asset-item>`, or a `<video>` / `<audio>` element when the file extension is a video / audio one (an audio asset must not be an `<a-asset-item>` - see the `sound` component note in CLAUDE.md).

### `addEntity(tag = "a-entity", attributes = {}, id = "", parent = null)`

Creates an element of any tag, sets each entry of `attributes` via `setAttribute`, and appends it to `parent` (or the scene, by default). This is what every `add*` convenience method below is built on — reach for it directly whenever you need a tag or component that doesn't have its own wrapper.

| Param | Type | Description |
|---|---|---|
| `tag` | `string` | Element tag to create, e.g. `"a-entity"`, `"a-box"`, `"a-text"`. |
| `attributes` | `object` | Map of attribute/component name → value. Values can be strings or objects, exactly like A-Frame's own `setAttribute`. |
| `id` | `string` | Optional element id. |
| `parent` | `Element` | Optional parent to append to. Defaults to the scene. |

Returns the created element.

```js
discovr.addEntity("a-text", { value: "Hello DiscoVR", color: "white" }, "greeting");
```

### `addBox(attributes = {}, id = "", parent = null)`
### `addSphere(attributes = {}, id = "", parent = null)`
### `addPlane(attributes = {}, id = "", parent = null)`
### `addLight(attributes = {}, id = "", parent = null)`

Convenience wrappers over `addEntity()` for `a-box`, `a-sphere`, `a-plane`, and `a-light` respectively. Same parameters and return value as `addEntity()`.

```js
discovr.addPlane({ width: 10, height: 10, color: "gray", rotation: "-90 0 0" }, "floor");
discovr.addLight({ type: "directional", intensity: 1.2 });
```

### `addModel(assetId, attributes = {}, id = "", parent = null)`

Adds an `a-entity` with `gltf-model` pointing at a previously-registered asset.

| Param | Type | Description |
|---|---|---|
| `assetId` | `string` | Id of an asset added via `addAsset()`. |
| `attributes` | `object` | Additional attributes/components for the entity (position, scale, ...). |
| `id` | `string` | Optional element id. |
| `parent` | `Element` | Optional parent to append to. Defaults to the scene. |

```js
discovr.addAsset("chair", "models/chair.glb");
discovr.addModel("chair", { position: "1 0 -2" }, "livingRoomChair");
```

### `addTestCube(idname = "")`

Adds a simple red box — useful as a quick sanity check that a scene is rendering. Equivalent to `addBox({ color: "red" }, idname)`.

## Nesting entities

Every `add*` method accepts a `parent`, so you can build a hierarchy the same way you would with nested HTML tags:

```js
var group = discovr.addEntity("a-entity", { position: "0 1 -3" }, "group");
discovr.addBox({ color: "red" }, "child1", group);
discovr.addSphere({ color: "blue", position: "1 0 0" }, "child2", group);
```

## What's next

- **[Message bus](bus.md)** — `DiscoVR.bus`, a page-wide publish/subscribe bus (available before the scene exists) for components and scripts to talk to each other, and the `i-bus` component to wire objects together without code.
- **[Examples](examples.md)** — a virtual tour built with `addModel()`, plus a few other complete pages.
