# Creating reusable objects

A reusable object owns a model and its supported behavior. The consuming application owns scene composition, input, placement, simulation, and shared services. The engine provides generic services; it does not import the object library. See the [object package reference](object_packages.html) for existing classes and their specific contracts.

## Keep the package independent

Create a folder such as `objects/marker/` containing `index.js`, `object.json`, and any local helper or asset files. Every import inside the package must resolve inside that same folder. Pass the engine namespace into the constructor as `new Marker(api, options)`; use `api.THREE` instead of loading another Three.js copy.

Use `new URL('./assets/example.png', import.meta.url)` for a package-local asset URL. Do not embed an application directory, hostname, original checkout path, or an import of a sibling object package. A renamed or relocated package then needs only an updated import in the consuming application.

## Implement a small object

This example uses the public `ObjectState` helper for transforms, snapshots, bounds, and cleanup. The marker's origin is at its base; dimensions are metres and rotations are radians.

```js
// objects/marker/index.js
export class Marker {
  constructor(api, {name = 'Marker', height = 1, color = '#cc8855'} = {}) {
    if (api.OBJECT_PROTOCOL !== 1) throw new Error('Object protocol 1 required');
    if (!Number.isFinite(height) || height <= 0) throw new RangeError('Invalid height');
    this.api = api;
    this.state = new api.ObjectState({type: 'Marker', name, height, color});
    this.root = this.state.root;
    this.properties = this.state.properties;
    this.mesh = new api.THREE.Mesh(
      new api.THREE.BoxGeometry(.6, height, .6),
      new api.THREE.MeshStandardMaterial({color, roughness: .7})
    );
    this.mesh.position.y = height / 2;
    this.mesh.castShadow = this.mesh.receiveShadow = true;
    this.root.add(this.mesh);
  }
  get position() { return this.root.position; }
  get rotation() { return this.root.rotation; }
  get quaternion() { return this.root.quaternion; }
  get scale() { return this.root.scale; }
  setPosition(x, y, z) { this.position.set(x, y, z); return this; }
  setColor(color) {
    this.mesh.material.color.set(color);
    this.properties.color = color;
    return this;
  }
  update(dt, time, context) {} // No animation is needed for this object.
  getBounds() { return this.state.getBounds(); }
  serialize() { return this.state.serialize(); }
  restore(snapshot) { this.state.restore(snapshot); return this; }
  dispose() { this.state.dispose(); }
}
```

`root` is the Object3D added to a scene. `properties` records public state; use methods to make changes that also affect geometry or materials. `update` receives elapsed seconds and application time, with optional context supplied by the consumer. `getBounds()` must return finite world-space visual bounds. It is not automatically a collision shape.

`dispose()` must be safe to call twice. `ObjectState` disposes resources attached to this example's root. Register detached resources and listeners with `this.state.scope`; release replaced geometry, materials, and textures when rebuilding. Borrowed terrain, wind, engine services, or shared asset data remain caller-owned unless the object's contract explicitly says otherwise.

## Declare the package and Studio entry

```json
{
  "package": "marker",
  "version": "1.0.0",
  "entry": "index.js",
  "classes": ["Marker"],
  "engineObjectProtocol": 1,
  "externalFileDependencies": [],
  "studio": {"entry": "studio.js", "protocol": 1}
}
```

The Studio entry is optional for ordinary applications. To expose the object in Model Studio, add:

```js
// objects/marker/studio.js
import {Marker} from './index.js';

export const models = [{
  id: 'marker',
  label: 'Marker',
  create: api => new Marker(api),
  properties: [{
    id: 'color', label: 'Color', type: 'color',
    get: object => object.properties.color,
    set: (object, value) => object.setColor(value)
  }],
  actions: [{
    id: 'turn', label: 'Quarter turn',
    run: object => { object.rotation.y += Math.PI / 2; }
  }]
}];
```

In Studio, expand **Load another package**, enter `marker`, and press **Load package**. Choose the model and add it to the scene. There is no need to edit Studio's built-in catalogue. Packages are trusted application code; the loader is not a sandbox or a general-purpose mesh-file importer.

Studio factories may be asynchronous. Their context includes an abort signal, camera, terrain, quality, and shared wind. Check cancellation during loading and release allocations if construction fails. A successfully returned object becomes viewer-owned. Property types, actions, cancellation, and loading boundaries are specified in the [Model Studio guide](model_studio.html). The existing `tests/fixtures/studio-object/` is a working package example.

## Save state and reinject dependencies

The example's generic `restore()` applies transforms. To restore constructor-dependent height and color, reconstruct first:

```js
const saved = marker.serialize();
marker.dispose();
marker = new Marker(api, saved.properties).restore(saved);
scene.add(marker.root);
```

A snapshot is not an executable scene. Reinject borrowed context when rebuilding. For example, await `loadAuthoredAssets()` once and pass the returned `assets` into every reconstructed `AuthoredPerson`; provide terrain and wind again when required. Do not serialize live renderers, functions, or service instances as though they were portable JSON.

## Verify and document

From the complete development project, run `node tools/verify.mjs --root=objects/marker` to check package closure. Then copy the package into an independent consumer with its own engine copy. Check actual rendering, property/action behavior, bounds, reconstruction, and repeated disposal. Static import checks alone cannot establish those behaviors.

Document constructor options, required API/services, units and axes, resource ownership, and snapshot limits under `docs/`. Keep licenses and asset provenance with the distribution. The [API reference](api.html), [getting started guide](getting-started.html), and [distribution guide](distribution-guide.html) cover the surrounding integration.
