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:
| Form | Meaning | Example |
|---|---|---|
//realm/path | Explicit resource namespace, resolved through Orion or looked up in the UnitTree | //rhea/shaders/standard |
/path | Relative to the project's resource root | /textures/grass.png |
name or folder/name | Local object reference within the configured scene instance | tavern/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 property | Purpose |
|---|---|
material | Explicit material override for the mesh's submeshes |
texture | Default diffuse/basecolor texture override |
diffuse_tex | List of diffuse/basecolor texture overrides |
normal_tex | List of normal-map overrides |
shader | Shader 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:
| Target | Reference type |
|---|---|
| Parsed character or scene definition | Ref<SceneResource> |
| Mesh asset | Ref<Mesh> |
| Image/texture resource | Ref<ImageResource> |
| Shader resource | Ref<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:
| Usage | Meaning |
|---|---|
Load | Make the referenced resource available |
Prepare | Invoke and await its preparation hook, for example OBJ dependency preparation or a reusable scene prototype |
EnsureReady | Await its ready/materialized form, for example a font library scene |
Instantiate | Build 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:
- Populate their collection during setup or parsing.
- Call the base
resolveReferences()and normalize each Ref usingnormalizeReference(ref, context). - Call the base
getReferences()and append their current entries with unique names. - 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
| Symptom | Check |
|---|---|
| An override is ignored without a file-load message | A bare filename may be interpreted as a local object reference. Use /textures/name.png for a project file. |
//world1/... is not found | A 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 found | Ensure the library is materialized, not only loaded as a scene definition. |
| A reference is correct in a template but wrong in its instance | Check scalar registration and dynamic collection copying after target setup. |
| A local mesh or font reference is not found | Inspect both Ref::id() and Ref::basePath(). Do not replace an existing base path with an empty attribute. |
| A character definition is loaded but cannot spawn | The reference needs Prepare, not only Load. |
| Two imported models display the same image | GPU texture identity must use the complete resource path, not a shared local name such as image_0. |