17 require(o,
"material field must be an object");
22 require(
a,
"material collection must be an array");
27 "material reference index out of range");
28 return size_t(
value->asInt());
34 double adjust(
double value,
double replacement, std::string_view field) {
36 throw Rejection{
path +
"." + std::string(field) +
": material factor out of range"};
39 "warning: material factor " + std::to_string(
value) +
" clamped to " + std::to_string(replacement),
44 std::string_view field =
"factor") {
45 if (!
value)
return fallback;
46 require(
value->isNumeric(),
"material factor must be numeric");
47 const double n =
value->isInt64() ? double(
value->asInt()) :
value->asDouble();
48 require(std::isfinite(
n),
"material factor must be finite");
52 std::string_view field =
"vector") {
55 for (
auto n : fallback)
a.emplace_back(
n);
56 return Value(std::move(
a));
58 const auto&
a = array(
value);
59 require(
a.size() == fallback.size(),
"invalid material factor vector");
61 for (
size_t i = 0; i <
a.size(); ++i)
62 out.emplace_back(scalar(&
a[i], 0,
minimum,
maximum, std::string(field) +
"[" + std::to_string(i) +
"]"));
63 return Value(std::move(out));
68 require(
r.ok(),
"invalid material identity");
69 return std::move(
r).takeValue();
71void budget(
const GltfImportRequest&
request,
const PreparedAssetImport& out) {
72 require(out.manifest.assets.size() <=
request.limits.maximumAssets,
"material asset budget exceeded");
74 for (
const auto& e : out.
entries) {
75 require(e.bytes.size() <=
request.limits.maximumDecodedBytes -
size,
"material byte budget exceeded");
76 size += e.bytes.size();
79void link(PreparedAssetImport& out,
const AssetRef&
from,
const AssetRef&
to, std::string role, std::string schema) {
80 out.manifest.dependencies.push_back(
84 const std::vector<std::span<const uint8_t>>& buffers, PreparedAssetImport& out) {
85 const auto* materialsValue =
member(
root,
"materials");
86 if (!materialsValue) {
87 const auto& meshes = array(
member(
root,
"meshes"));
88 for (
const auto&
mesh : meshes)
90 require(!
member(
object(primitive),
"material"),
"primitive material collection is missing");
93 const auto& materials = array(materialsValue);
94 std::map<std::string, AssetRef> images;
95 for (
size_t m = 0;
m < materials.size(); ++
m) {
97 Factors factors{
request, out,
"materials[" + std::to_string(
m) +
"]"};
100 const auto* pbrValue =
member(
source,
"pbrMetallicRoughness");
101 const auto&
pbr = pbrValue ?
object(*pbrValue) : empty;
102 auto ref =
reference(
request.package.packageId.child(
"gltf:material:" + std::to_string(
m)));
103 std::string mode =
"OPAQUE";
105 require(
v->isString(),
"alphaMode must be a string");
106 mode =
v->asString();
108 require(mode ==
"OPAQUE" || mode ==
"MASK" || mode ==
"BLEND",
"unknown alphaMode");
110 {
"schemaVersion",
Value(int64_t(15))},
111 {
"alphaToCoverage",
Value(
false)},
112 {
"shadingModel",
Value(
"pbr")},
113 {
"surfaceMode",
Value(mode ==
"OPAQUE" ?
"opaque"
114 : mode ==
"MASK" ?
"masked"
116 {
"baseColor", factors.vector(
member(
pbr,
"baseColorFactor"), {1, 1, 1, 1})},
117 {
"metallic",
Value(factors.scalar(
member(
pbr,
"metallicFactor"), 1))},
118 {
"roughness",
Value(factors.scalar(
member(
pbr,
"roughnessFactor"), 1))},
119 {
"emissive", factors.vector(
member(
source,
"emissiveFactor"), {0, 0, 0})},
122 require(
v->isBool(),
"doubleSided must be boolean");
123 definition[
"doubleSided"] = *
v;
125 definition[
"doubleSided"] =
Value(
false);
126 if (mode ==
"BLEND") definition[
"blendMode"] =
Value(
"alpha");
128 require(
name->isString() &&
name->asString().size() <=
request.limits.maximumStringBytes,
129 "material name exceeds budget");
130 definition[
"name"] = *
name;
133 for (
const auto* role :
134 {
"baseColorTexture",
"metallicRoughnessTexture",
"normalTexture",
"occlusionTexture",
"emissiveTexture"}) {
136 std::string_view(role) ==
"baseColorTexture" || std::string_view(role) ==
"metallicRoughnessTexture"
140 if (
v) textureInfos[role] = *
v;
142 const double unbounded = std::numeric_limits<double>::max();
143 if (
const auto* extensions =
member(
source,
"extensions")) {
144 const auto& materialExtensions =
object(*extensions);
145 require(!materialExtensions.contains(
"KHR_materials_unlit") || materialExtensions.size() == 1,
146 "unlit cannot be combined with PBR material extensions");
147 for (
const auto& [
name,
value] : materialExtensions) {
149 "unknown material extension");
151 auto factor = [&](
const char*
key,
double fallback,
double minimum = 0,
double maximum = 1) {
154 auto texture = [&](
const char*
key) {
157 if (
name ==
"KHR_materials_specular") {
158 factor(
"specularFactor", 1);
159 definition[
"specularColorFactor"] =
160 factors.vector(
member(ext,
"specularColorFactor"), {1, 1, 1}, 0, unbounded);
161 texture(
"specularTexture");
162 texture(
"specularColorTexture");
163 }
else if (
name ==
"KHR_materials_anisotropy") {
164 factor(
"anisotropyStrength", 0);
165 factor(
"anisotropyRotation", 0, -unbounded, unbounded);
166 texture(
"anisotropyTexture");
167 }
else if (
name ==
"KHR_materials_clearcoat") {
168 factor(
"clearcoatFactor", 0);
169 factor(
"clearcoatRoughnessFactor", 0);
170 texture(
"clearcoatTexture");
171 texture(
"clearcoatRoughnessTexture");
172 texture(
"clearcoatNormalTexture");
173 }
else if (
name ==
"KHR_materials_emissive_strength") {
174 factor(
"emissiveStrength", 1, 0, unbounded);
175 }
else if (
name ==
"KHR_materials_ior") {
176 double ior = factors.scalar(
member(ext,
"ior"), 1.5, 0, unbounded,
"ior");
177 if (ior > 0 && ior < 1) ior = factors.adjust(ior, 1,
"ior");
178 definition[
"ior"] =
Value(ior);
179 }
else if (
name ==
"KHR_materials_unlit")
180 definition[
"shadingModel"] =
Value(
"unlit");
183 for (
const auto& [role, textureInfo] : textureInfos) {
185 role ==
"baseColorTexture" || role ==
"emissiveTexture" || role ==
"specularColorTexture";
188 if (effectiveUv) require(effectiveUv->isInt64() && effectiveUv->asInt() >= 0,
"invalid texCoord");
189 if (
const auto* extensions =
member(
info,
"extensions")) {
191 require(
name ==
"KHR_texture_transform",
"unknown texture-info extension");
193 if (
auto*
uv =
member(transform,
"texCoord")) effectiveUv =
uv;
195 {
"offset", factors.vector(
member(transform,
"offset"), {0, 0}, -unbounded, unbounded)},
196 {
"scale", factors.vector(
member(transform,
"scale"), {1, 1}, -unbounded, unbounded)},
197 {
"rotation",
Value(factors.scalar(
member(transform,
"rotation"), 0, -unbounded, unbounded))},
198 {
"texCoord", effectiveUv ? *effectiveUv :
Value(int64_t(0))}});
202 require(effectiveUv->isInt64() && effectiveUv->asInt() >= 0 &&
203 uint64_t(effectiveUv->asInt()) <= std::numeric_limits<uint32_t>::max(),
204 "invalid effective UV set");
205 const auto uvSet = effectiveUv ? uint32_t(effectiveUv->asInt()) : 0
u;
206 if (!definition.contains(role +
"Transform")) definition[role +
"TexCoord"] =
Value(int64_t(uvSet));
209 const auto&
p =
object(primitive);
210 const auto* materialIndex =
member(
p,
"material");
211 if (!materialIndex ||
index(materialIndex, materials.size()) !=
m)
continue;
212 const auto* attributes =
member(
p,
"attributes");
213 require(attributes &&
object(*attributes).contains(
"TEXCOORD_" + std::to_string(uvSet)),
214 "texture UV set is absent from a bound primitive");
217 const auto& textures = array(
member(
root,
"textures"));
219 require(!
member(texture,
"extensions"),
"texture extensions require explicit translation");
220 const auto& sourceImages = array(
member(
root,
"images"));
221 auto imageIndex =
index(
member(texture,
"source"), sourceImages.size());
222 const auto key = std::to_string(imageIndex) +
":" + role;
224 if (
found == images.end()) {
225 const auto&
image =
object(sourceImages[imageIndex]);
226 std::vector<uint8_t>
bytes;
228 require(
uri->isString(),
"image URI must be a string");
230 require(resource !=
request.externalResources.end(),
"image URI was not supplied explicitly");
233 const auto& views = array(
member(
root,
"bufferViews"));
238 require(
offset.ok() &&
length.ok(),
"invalid image buffer range");
239 require(
offset.value() <= buffers[
b].size() &&
length.value() <= buffers[
b].size() -
offset.value(),
240 "image buffer range out of bounds");
241 auto span = buffers[
b].subspan(
size_t(
offset.value()),
size_t(
length.value()));
242 bytes.assign(span.begin(), span.end());
244 auto identity =
request.package;
245 identity.packageId = identity.packageId.child(
"gltf:image:" +
key);
250 auto imageRef =
imported.value().manifest.assets.front().asset;
251 for (
auto&
a :
imported.
value().manifest.assets) out.manifest.assets.push_back(
std::move(
a));
254 if (e.
path.starts_with(
"assets/")) out.
entries.push_back(
std::move(e));
255 found = images.emplace(
key, imageRef).first;
258 definition[role] =
Value(
found->second.format());
259 link(out, ref,
found->second, role,
"eve.image/3");
261 if (
auto*
id =
member(texture,
"sampler")) {
262 const auto& samplers = array(
member(
root,
"samplers"));
263 const auto& supplied =
object(samplers[
index(
id, samplers.size())]);
264 for (
const auto& [
name,
value] : supplied) {
265 require(
value.isInt64(),
"sampler fields must be integer enums");
267 require((
name ==
"wrapS" ||
name ==
"wrapT") ? (
n == 33071 ||
n == 33648 ||
n == 10497)
268 :
name ==
"magFilter" ? (
n == 9728 ||
n == 9729)
269 :
name ==
"minFilter" ? (
n == 9728 ||
n == 9729 || (
n >= 9984 &&
n <= 9987))
271 "unsupported sampler field");
275 definition[std::string(role) +
"Sampler"] =
Value(std::move(
sampler));
276 if (role ==
"normalTexture" || role ==
"clearcoatNormalTexture")
277 definition[role ==
"normalTexture" ?
"normalScale" :
"clearcoatNormalScale"] =
279 if (std::string_view(role) ==
"occlusionTexture")
280 definition[
"occlusionStrength"] =
Value(factors.scalar(
member(
info,
"strength"), 1));
282 const auto path =
"assets/" + ref.id().format() +
"/asset.json";
283 auto json =
Value(std::move(definition)).toJson();
284 require(json.ok(),
"cannot encode material");
285 std::vector<uint8_t>
bytes(json.value().begin(), json.value().end());
286 out.manifest.assets.push_back(
288 out.entries.push_back({
path, std::move(
bytes)});
289 out.manifest.entrypoints.emplace(
"material:" + std::to_string(
m), ref);
290 out.findings.push_back({
request.sourceName,
"materials[" + std::to_string(
m) +
"]",
292 "PBR material, image semantics and sampler bindings retained"});
295 const auto& meshes = array(
member(
root,
"meshes"));
296 for (
size_t m = 0;
m < meshes.size(); ++
m) {
297 const auto& primitives = array(
member(
object(meshes[
m]),
"primitives"));
298 for (
size_t p = 0;
p < primitives.size(); ++
p) {
299 const auto*
value =
member(
object(primitives[
p]),
"material");
300 if (!
value)
continue;
302 request.package.packageId.child(
"gltf:material:" + std::to_string(
index(
value, materials.size()))));
304 request.package.packageId.child(
"gltf:mesh:" + std::to_string(
m) +
":primitive:" + std::to_string(
p)));
305 link(out,
mesh,
material,
"material",
"eve.material/15");
311 return name ==
"KHR_materials_specular" ||
name ==
"KHR_materials_anisotropy" ||
312 name ==
"KHR_materials_clearcoat" ||
name ==
"KHR_materials_emissive_strength" ||
313 name ==
"KHR_materials_ior" ||
name ==
"KHR_materials_unlit" ||
name ==
"KHR_texture_transform";
320 }
catch (
const Rejection&
error) {
AuthorityStoreHandleRef reference
std::unordered_map< std::string, QuestRuntime > entries
const GltfImportRequest & request
vk::UniqueSampler sampler
HexCoordinates to
Cell the unit walks towards on this segment.
float(ui::Theme::* member)[4]
const UnitySourceAsset & source
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Move-only operation result carrying either a value or Status.
static Result success(T value)
Construct a successful result owning value.
static Result failure(Status status)
Construct a failed result from a structured status.
std::map< std::string, Value > Object
std::vector< Value > Array
std::string sha256(std::span< const std::uint8_t > bytes)
Sha 256.
Result< AssetRef > assetRef(PersistentId id)
Asset ref.
bool supportsMaterialExtension(std::string_view name)
Whether this adapter translates the named standard material/texture extension.
Result< std::uint64_t > unsignedValue(const Value *value, std::string path, bool required, std::uint64_t fallback)
Validate an optional integer field. @ownership Borrowed nullable field owned by the input document....
Result< void > appendMaterials(const GltfImportRequest &request, const Value::Object &root, const std::vector< std::span< const uint8_t > > &buffers, PreparedAssetImport &output)
EVENGINE_API_PLATFORM Result< PreparedAssetImport > prepareImageImport(const ImageImportRequest &request)
Decode image metadata and prepare one canonical eve.image source asset.
std::variant< std::monostate, std::int64_t, double, std::string, bool > Value
StrongUuid< detail::PersistentIdTag > PersistentId
Stable instance identity for persistence, networking and process boundaries.
detail::StrongUint64< detail::SchemaVersionTag > SchemaVersion
Persistent data-format version; not a runtime replacement generation.
Untrusted .gltf/.glb input plus explicitly supplied external URI bytes.
Owning, validated source archive candidate not yet published to disk/database.