Skip to main content

Asset References

Rhea uses resource IDs to identify files and objects in the UnitTree. A resource ID is not necessarily a physical filename: it can also name a mesh, material, texture, or other object defined inside a scene resource.

This guide describes the resource-context and typed-reference implementation. Older releases may use different rules for paths beginning with a single /.

Choosing a reference​

In scene configuration, use these forms:

FormMeaningExample
//realm/pathExplicit resource namespace, resolved through Orion or looked up in the UnitTree//rhea/shaders/standard
/pathRelative to the project's resource root/textures/grass.png
name or folder/nameLocal object reference within the configured scene instancetavern/mesh

A filename without a leading slash is not automatically a request to load a file. For example, darkwood.png can be interpreted as a local object name. Use /darkwood.png to refer to a file in the project root.

An explicit realm identifies a namespace, not a scene name. Setting name="world1" does not register //world1. Orion must already know how to resolve an explicitly named realm.

Project root and source directory​

When a new entry resource is loaded without a supplied context, its containing directory becomes the context root. Dependencies inherit this root; loading a scene from a subdirectory does not make that subdirectory the new project root.

For example, opening:

//user/Rhea/World1/world1.scene

establishes:

context_root = //user/Rhea/World1

These references then identify:

/scenes/interior.scene -> //user/Rhea/World1/scenes/interior.scene
/textures/grass.png -> //user/Rhea/World1/textures/grass.png

The //user realm maps to the actual user home directory. On Windows, the example can resolve to C:\Users\acme\Rhea\World1\world1.scene; on Linux to /home/users/acme/Rhea/World1/world1.scene.

The -d command-line option still sets the physical directory mapped by //local. A project opened as //local/world1.scene therefore has //local as its context root. Packaged engine and application assets can keep explicit namespaces such as //rhea and //ceto.

The same mechanism also works with a registered logical namespace. Opening //meworla/world1/world.scene establishes //meworla/world1 as the context root, provided Orion can resolve meworla. Its physical location can differ between installations.

Referencing configured objects​

A local object reference uses the scene instance's basepath, rather than the directory of an asset file. Consider:

[scene]
name="scene1"

[tavern]
type="//meworla/item"
mesh="tavern/mesh"

[tavern/mesh]
type="//meworla/mesh"
geometry="tavern/tavernfloor"

[tavern/tavernfloor]
type="//meworla/wavefront"
path="/models/tavern.obj"
material="/models/tavern.mtl"

tavern/mesh and tavern/tavernfloor name objects defined in this scene. The OBJ and MTL paths name files under the project root. Local references are based on the configured scene instance, not automatically on the immediate parent of the component containing the reference.

The mesh property on an Item is a shorthand for creating a MeshComponent. The explicit form uses:

[tavern/mc]
type="//meworla/meshcomponent"
meshRef="tavern/mesh"

Use one form for a given item rather than configuring both independently.

Resources defined in another scene​

A resource library can materialize named objects. If resources1.scene has a root named resources and a child texture named grass, that object can be referenced as:

texture="/resources/grass"

This refers to the texture object, not a file named resources/grass. The library must be materialized before its objects are used. Loading its parsed scene definition alone does not create those objects. See Runtime Scene Instancing for the distinction between definitions, preparation, and instances.

Geometry imports and material overrides​

Geometry imports supply their original materials and textures. Mesh properties apply overrides after the import, so different meshes can use the same geometry with different appearances.

[tavern_mesh]
type="//meworla/mesh"
geometry="tavern_geometry"
diffuse_tex=("/textures/brick.png")

[tavern_geometry]
type="//meworla/wavefront"
path="/models/tavern.obj"
material="/models/tavern.mtl"

Move legacy OBJ diffuse_tex and normal_tex overrides from the geometry definition to the mesh definition.

Mesh propertyPurpose
materialExplicit material override for the mesh's submeshes
textureDefault diffuse/basecolor texture override
diffuse_texList of diffuse/basecolor texture overrides
normal_texList of normal-map overrides
shaderShader for a generated default material; it does not automatically replace every imported material's shader

An empty texture reference means no override. Existing imported textures remain in use. A shader must expose the appropriate texture slot for an override to take effect.

The current Mesh implementation selects list entries by submesh index, repeating the list with modulo indexing. It does not match entries by imported material name. Keep this limitation in mind for models with multiple materials.

Materials must be copied before applying mesh-specific overrides to shared source materials. Otherwise one mesh can change the appearance of another mesh using the same geometry.

OBJ and MTL dependencies​

The material property of an OBJ geometry names its MTL source file. It is distinct from a Mesh's material override.

An MTL file can reference a diffuse texture:

newmtl Ground
Kd 1.0 1.0 1.0
map_Kd textures/grass.jpg

textures/grass.jpg is relative to the MTL file's directory. It is not a local UnitTree reference and is not relative to the scene's project root. For /models/tavern.mtl, it identifies /models/textures/grass.jpg within the project.

OBJ preparation parses the source data and loads discovered image dependencies before the geometry is used to build a mesh. Missing images should be diagnosed; do not assume that a successfully parsed MTL guarantees every texture was loaded.

glTF and GLB dependencies​

A mesh can reference a model file directly:

[cube_mesh]
type="//meworla/mesh"
geometry="/models/cube/cube.gltf"

The glTF file loader creates the Geometry resource. No separate OBJ-style geometry section is needed.

External images[].uri and buffers[].uri entries are resolved relative to the glTF/GLB file's directory and loaded through Orion. Data URIs and embedded GLB buffer data stay embedded. The loader completes only after the required dependency-loading and parsing work has finished.

The current glTF loader reports external dependency failures as load errors, including missing images. Do not rely on missing-texture fallback behavior being identical between OBJ and glTF.

Defining references in C++ components​

Ref<T> stores an ID and an optional base path for local object lookup. T is the type of the referenced object, not necessarily the type produced by instantiating it:

TargetReference type
Parsed character or scene definitionRef<SceneResource>
Mesh assetRef<Mesh>
Image/texture resourceRef<ImageResource>
Shader resourceRef<Shader>

For example, a character template is a SceneResource; spawning it produces the character instance.

Register a scalar reference once in the declaring class's initializeType():

void MeshComponent::initializeType(simpleparser::Type* type)
{
registerResourceProperty(
type, "meshRef", &MeshComponent::m_meshRef,
"//meworla/mesh", ResourceUsage::Load);
}

The registered name must match the configuration property exactly. Component::setup() copies a supplied value into the registered Ref member. Missing values leave existing defaults intact. Set defaults on the Ref itself, rather than only changing m_values after setup.

The generic resolveReferences() and getReferences() process the registered properties of the concrete type and its base types. Classes containing only registered scalar references do not need their own overrides. Type initialization registers each class's own properties; do not re-register inherited properties.

Loading and looking up are different operations​

Reference enumeration tells the loading pipeline which dependencies are required. UnitTree::resolve() only looks up an object that is already available:

Mesh* mesh = app->getUnitTree()->resolve(m_mesh_ref);

It does not load a missing file. It uses the Ref's base path for local object references and leaves fully qualified IDs unchanged.

Resource usage controls the additional lifecycle work:

UsageMeaning
LoadMake the referenced resource available
PrepareInvoke and await its preparation hook, for example OBJ dependency preparation or a reusable scene prototype
EnsureReadyAwait its ready/materialized form, for example a font library scene
InstantiateBuild an instance and attach it to the requesting parent

A local configured Geometry also needs Prepare, even though no file needs to be loaded to find that Geometry object. The scene builder passes the existing object to the dependency pipeline instead of treating its object path as a filename.

Context and instantiation​

The loading context contains:

struct ResourceContext {
std::string context_root;
std::string source_directory;
};

context_root is inherited from the entry resource. source_directory is updated for each loaded file. Neither is the same as the UnitTree instance's basepath.

At the low-level loading API, a relative file ID can be joined to source_directory. That does not change the scene-configuration rule: an unprefixed configured reference remains a local object reference. Importer file URIs are another distinct case and use the imported file's directory.

The current normalizeResourceId() joins these prefixes; it is not a general URI or path canonicalizer. In particular, do not assume that . and .. have been canonicalized into identical cache IDs.

During scene construction, configure objects first, then resolve and collect references across the resulting instance tree, including components created during setup. Complete dependency work before finalizing objects for use.

Registered scalar Ref members are copied after target setup during instantiation, preserving their normalized IDs and base paths. This avoids target setup replacing a prepared reference with an original configuration string.

Dynamic collections​

Maps and vectors of references remain the responsibility of their owning class. For example, a font family owns its map of sizes to Ref<Font> values.

Such classes should:

  1. Populate their collection during setup or parsing.
  2. Call the base resolveReferences() and normalize each Ref using normalizeReference(ref, context).
  3. Call the base getReferences() and append their current entries with unique names.
  4. In instanceSetup(), call the direct base implementation and then copy the complete Ref collection to the target.

Copy the Ref values, including base paths, rather than reconstructing them from the original strings. Separately resolved runtime caches can be rebuilt for the target instance. For dependencies discovered while parsing a file, loading must also wait for those newly discovered dependencies.

Troubleshooting​

SymptomCheck
An override is ignored without a file-load messageA bare filename may be interpreted as a local object reference. Use /textures/name.png for a project file.
//world1/... is not foundA scene's name does not register an Orion realm. Use a project-relative reference or register the namespace explicitly.
An object from resources.scene is not foundEnsure the library is materialized, not only loaded as a scene definition.
A reference is correct in a template but wrong in its instanceCheck scalar registration and dynamic collection copying after target setup.
A local mesh or font reference is not foundInspect both Ref::id() and Ref::basePath(). Do not replace an existing base path with an empty attribute.
A character definition is loaded but cannot spawnThe reference needs Prepare, not only Load.
Two imported models display the same imageGPU texture identity must use the complete resource path, not a shared local name such as image_0.