Skip to main content

khora_io/asset/decoders/mesh/
resource_resolver.rs

1// Copyright 2025 eraflo
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! External-resource resolution for complex asset formats like GLTF.
16
17use std::error::Error;
18use std::path::{Path, PathBuf};
19
20/// Resolves external resources (buffers, images) referenced by a URI within
21/// an asset file.
22pub trait GltfResourceResolver: Send + Sync {
23    /// Resolves an external buffer URI to its binary data.
24    fn resolve_buffer(&self, uri: &str) -> Result<Vec<u8>, Box<dyn Error + Send + Sync>>;
25
26    /// Resolves an external image URI to its binary data.
27    fn resolve_image(&self, uri: &str) -> Result<Vec<u8>, Box<dyn Error + Send + Sync>>;
28}
29
30/// Resolves resources from the local filesystem relative to a base path.
31pub struct FileSystemResolver {
32    base_path: PathBuf,
33}
34
35impl FileSystemResolver {
36    /// Creates a new `FileSystemResolver` with a specified base path.
37    pub fn new(base_path: impl AsRef<Path>) -> Self {
38        Self {
39            base_path: base_path.as_ref().to_path_buf(),
40        }
41    }
42}
43
44impl GltfResourceResolver for FileSystemResolver {
45    fn resolve_buffer(&self, uri: &str) -> Result<Vec<u8>, Box<dyn Error + Send + Sync>> {
46        let path = self.base_path.join(uri);
47        std::fs::read(&path)
48            .map_err(|e| format!("Failed to read external buffer from '{:?}': {}", path, e).into())
49    }
50
51    fn resolve_image(&self, uri: &str) -> Result<Vec<u8>, Box<dyn Error + Send + Sync>> {
52        let path = self.base_path.join(uri);
53        std::fs::read(&path)
54            .map_err(|e| format!("Failed to read external image from '{:?}': {}", path, e).into())
55    }
56}
57
58/// Resolver that fails on every external URI lookup. Suitable as a default
59/// for self-contained `.glb` files (which embed all buffers + images) and
60/// as a placeholder when no project context is available.
61///
62/// `MeshDispatcher::default` uses this. For `.gltf` files referencing
63/// external `.bin` / texture buffers, construct
64/// [`MeshDispatcher::new(Arc::new(FileSystemResolver::new(...)))`] instead.
65pub struct NoOpResourceResolver;
66
67impl GltfResourceResolver for NoOpResourceResolver {
68    fn resolve_buffer(&self, uri: &str) -> Result<Vec<u8>, Box<dyn Error + Send + Sync>> {
69        Err(format!(
70            "NoOpResourceResolver cannot fetch external buffer '{}'; \
71             use FileSystemResolver for .gltf files with external buffers.",
72            uri
73        )
74        .into())
75    }
76    fn resolve_image(&self, uri: &str) -> Result<Vec<u8>, Box<dyn Error + Send + Sync>> {
77        Err(format!(
78            "NoOpResourceResolver cannot fetch external image '{}'; \
79             use FileSystemResolver for .gltf files with external textures.",
80            uri
81        )
82        .into())
83    }
84}