11struct Invalid : std::runtime_error {
12 using std::runtime_error::runtime_error;
17uint32_t u32(std::span<const std::byte>
bytes,
size_t i) {
18 require(i <=
bytes.size() &&
bytes.size() - i >= 4,
"Truncated GLB integer");
19 return uint32_t(
bytes[i]) | uint32_t(
bytes[i + 1]) << 8 | uint32_t(
bytes[i + 2]) << 16 |
20 uint32_t(
bytes[i + 3]) << 24;
23 require(
value.isInt64(),
"Reference must be an integer");
24 const auto n =
value.asInt64(-1);
25 require(
n >= 0 &&
static_cast<uint64_t
>(
n) <
size,
"Reference is out of range");
26 return static_cast<int>(
n);
29 if (!
value)
return fallback;
30 require(
value.isNumber() && std::isfinite(
value.asDouble()) &&
31 std::abs(
value.asDouble()) <= std::numeric_limits<float>::max(),
32 "Expected finite number");
33 return value.asFloat();
36std::array<float, N> vector(J
value, std::array<float, N> fallback) {
37 if (!
value)
return fallback;
38 require(
value.isArray() &&
value.size() == N,
"Invalid vector size");
39 for (
size_t i = 0; i < N; ++i) fallback[i] =
number(
value.at(i), 0);
42std::string choice(J
value, std::string fallback, std::initializer_list<const char*> allowed) {
43 if (!
value)
return fallback;
44 require(
value.isString(),
"Expected enum string");
46 for (
auto x : allowed)
48 throw Invalid(
"Unknown enum value: " +
s);
53 const auto textures =
root.get(
"textures");
54 auto definition = textures.at(
index(
info.get(
"index"), textures.size()));
55 t.image =
index(definition.get(
"source"),
root.get(
"images").size());
56 require(
info.getInt(
"texCoord", 0) == 0,
"VRM texture requires unsupported UV channel");
57 if (definition.has(
"sampler")) {
58 auto sampler =
root.get(
"samplers").at(
index(definition.get(
"sampler"),
root.get(
"samplers").size()));
59 t.wrapS =
sampler.getInt(
"wrapS", 10497);
60 t.wrapT =
sampler.getInt(
"wrapT", 10497);
62 auto x =
info.get(
"extensions").get(
"KHR_texture_transform");
63 t.offset = vector<2>(
x.get(
"offset"),
t.offset);
64 t.scale = vector<2>(
x.get(
"scale"),
t.scale);
65 t.rotation =
number(
x.get(
"rotation"), 0);
66 require(
x.getInt(
"texCoord", 0) == 0,
"Texture transform requires unsupported UV channel");
69void readMaterials(VrmDocument&
d, J
root) {
70 auto materials =
root.get(
"materials");
71 for (
size_t i = 0; i < materials.size(); ++i) {
72 auto j = materials.at(i),
p = j.get(
"pbrMetallicRoughness");
74 m.name = j.getString(
"name");
75 m.color = vector<4>(
p.get(
"baseColorFactor"),
m.color);
76 m.emission = vector<3>(j.get(
"emissiveFactor"),
m.emission);
77 m.alphaMode = choice(j.get(
"alphaMode"),
"OPAQUE", {
"OPAQUE",
"MASK",
"BLEND"});
78 m.cutoff =
number(j.get(
"alphaCutoff"), .5f);
79 m.doubleSided = j.getBool(
"doubleSided");
80 m.textures[0] = texture(
p.get(
"baseColorTexture"),
root);
81 m.textures[2] = texture(j.get(
"normalTexture"),
root);
82 m.normalScale =
number(j.get(
"normalTexture").get(
"scale"), 1);
83 m.textures[4] = texture(j.get(
"emissiveTexture"),
root);
84 auto extensions = j.get(
"extensions"), mt = extensions.get(
"VRMC_materials_mtoon");
85 m.emissionStrength =
number(extensions.get(
"KHR_materials_emissive_strength").get(
"emissiveStrength"), 1);
87 require(mt.getString(
"specVersion") ==
"1.0",
"Unknown MToon version");
89 m.zWrite = mt.getBool(
"transparentWithZWrite");
90 m.queue = mt.getInt(
"renderQueueOffsetNumber");
91 m.shade = vector<3>(mt.get(
"shadeColorFactor"),
m.shade);
92 m.matcap = vector<3>(mt.get(
"matcapFactor"),
m.matcap);
93 m.rim = vector<3>(mt.get(
"parametricRimColorFactor"),
m.rim);
94 m.outline = vector<3>(mt.get(
"outlineColorFactor"),
m.outline);
95 m.shift =
number(mt.get(
"shadingShiftFactor"), 0);
96 m.toony =
number(mt.get(
"shadingToonyFactor"), .9f);
97 m.gi =
number(mt.get(
"giEqualizationFactor"), .9f);
98 m.rimMix =
number(mt.get(
"rimLightingMixFactor"), 1);
99 m.rimPower =
number(mt.get(
"parametricRimFresnelPowerFactor"), 1);
100 m.rimLift =
number(mt.get(
"parametricRimLiftFactor"), 0);
102 choice(mt.get(
"outlineWidthMode"),
"none", {
"none",
"worldCoordinates",
"screenCoordinates"});
103 m.outlineWidth =
number(mt.get(
"outlineWidthFactor"), 0);
104 m.outlineMix =
number(mt.get(
"outlineLightingMixFactor"), 1);
105 m.scrollX =
number(mt.get(
"uvAnimationScrollXSpeedFactor"), 0);
106 m.scrollY =
number(mt.get(
"uvAnimationScrollYSpeedFactor"), 0);
107 m.rotationSpeed =
number(mt.get(
"uvAnimationRotationSpeedFactor"), 0);
108 m.textures[1] = texture(mt.get(
"shadeMultiplyTexture"),
root);
109 m.textures[3] = texture(mt.get(
"shadingShiftTexture"),
root);
110 m.shiftTextureScale =
number(mt.get(
"shadingShiftTexture").get(
"scale"), 1);
111 m.textures[5] = texture(mt.get(
"matcapTexture"),
root);
112 m.textures[6] = texture(mt.get(
"rimMultiplyTexture"),
root);
113 m.textures[7] = texture(mt.get(
"outlineWidthMultiplyTexture"),
root);
114 m.textures[8] = texture(mt.get(
"uvAnimationMaskTexture"),
root);
116 d.materials.push_back(std::move(
m));
119void readExpressions(VrmDocument&
d, J vrm, J
root) {
120 auto expressions = vrm.get(
"expressions");
121 for (
auto group : {
"preset",
"custom"}) {
122 auto items = expressions.get(
group);
123 for (
const auto&
name : items.keys()) {
124 auto j = items.get(
name.c_str());
127 e.binary = j.getBool(
"isBinary");
128 e.overrideBlink = choice(j.get(
"overrideBlink"),
"none", {
"none",
"block",
"blend"});
129 e.overrideLookAt = choice(j.get(
"overrideLookAt"),
"none", {
"none",
"block",
"blend"});
130 e.overrideMouth = choice(j.get(
"overrideMouth"),
"none", {
"none",
"block",
"blend"});
131 auto binds = j.get(
"morphTargetBinds");
132 for (
size_t i = 0; i < binds.size(); ++i) {
133 auto b = binds.at(i);
134 VrmExpression::Morph
m;
135 m.node =
index(
b.get(
"node"),
d.nodes.size());
136 int mesh =
d.nodeMeshes[
m.node];
137 require(
mesh >= 0,
"Expression node has no mesh");
138 auto primitives =
root.get(
"meshes").at(
mesh).get(
"primitives");
139 require(primitives.size() > 0,
"Expression mesh has no primitives");
140 m.index =
index(
b.get(
"index"), primitives.at(0).get(
"targets").size());
141 for (
size_t p = 0;
p < primitives.size(); ++
p)
142 require(
size_t(
m.index) < primitives.at(
p).get(
"targets").size(),
143 "Inconsistent primitive morph count");
144 m.weight =
number(
b.get(
"weight"), 1);
145 require(
m.weight >= 0 &&
m.weight <= 1,
"Invalid morph weight");
146 e.morphs.push_back(
m);
148 binds = j.get(
"materialColorBinds");
149 for (
size_t i = 0; i < binds.size(); ++i) {
150 auto b = binds.at(i);
151 VrmExpression::Color
c;
152 c.material =
index(
b.get(
"material"),
d.materials.size());
153 c.type = choice(
b.get(
"type"),
"color",
154 {
"color",
"emissionColor",
"shadeColor",
"matcapColor",
"rimColor",
"outlineColor"});
155 c.target = vector<4>(
b.get(
"targetValue"), {});
156 e.colors.push_back(
c);
158 binds = j.get(
"textureTransformBinds");
159 for (
size_t i = 0; i < binds.size(); ++i) {
160 auto b = binds.at(i);
162 u.material =
index(
b.get(
"material"),
d.materials.size());
163 u.scale = vector<2>(
b.get(
"scale"),
u.scale);
164 u.offset = vector<2>(
b.get(
"offset"),
u.offset);
167 d.expressions.push_back(std::move(e));
171void readSpring(VrmDocument&
d, J
root) {
172 auto s =
root.get(
"extensions").get(
"VRMC_springBone");
174 require(
s.getString(
"specVersion") ==
"1.0",
"Unknown spring bone version");
175 auto cs =
s.get(
"colliders");
176 for (
size_t i = 0; i < cs.size(); ++i) {
179 c.node =
index(j.get(
"node"),
d.nodes.size());
180 auto shape = j.get(
"shape");
181 c.capsule =
shape.has(
"capsule");
182 require(
c.capsule !=
shape.has(
"sphere"),
"Collider needs exactly one shape");
183 auto x =
shape.get(
c.capsule ?
"capsule" :
"sphere");
184 c.offset = vector<3>(
x.get(
"offset"), {});
185 c.tail = vector<3>(
x.get(
"tail"), {});
186 c.radius =
number(
x.get(
"radius"), 0);
187 require(
c.radius >= 0,
"Negative collider radius");
188 d.colliders.push_back(
c);
190 auto groups =
s.get(
"colliderGroups"), springs =
s.get(
"springs");
191 for (
size_t i = 0; i < springs.size(); ++i) {
192 auto j = springs.at(i);
194 spring.name = j.getString(
"name");
195 if (j.has(
"center")) spring.center =
index(j.get(
"center"),
d.nodes.size());
196 auto joints = j.get(
"joints");
197 require(joints.isArray() && joints.size() > 0,
"Spring has no joints");
198 for (
size_t n = 0;
n < joints.size(); ++
n) {
199 auto x = joints.at(
n);
201 q.node =
index(
x.get(
"node"),
d.nodes.size());
202 q.radius =
number(
x.get(
"hitRadius"), 0);
203 q.stiffness =
number(
x.get(
"stiffness"), 1);
204 q.drag =
number(
x.get(
"dragForce"), .5f);
205 q.gravityPower =
number(
x.get(
"gravityPower"), 0);
206 q.gravity = vector<3>(
x.get(
"gravityDir"),
q.gravity);
207 require(
q.radius >= 0 &&
q.stiffness >= 0 &&
q.gravityPower >= 0 &&
q.drag >= 0 &&
q.drag <= 1,
208 "Invalid spring joint parameters");
212 require(
parent >= 0,
"Spring joints must follow the node hierarchy");
214 spring.joints.push_back(
q);
216 auto memberships = j.get(
"colliderGroups");
217 for (
size_t g = 0;
g < memberships.size(); ++
g) {
220 spring.colliders.push_back(
index(
members.at(
c),
d.colliders.size()));
222 d.springs.push_back(std::move(spring));
229 require(
bytes.size() >= 20 && u32(
bytes, 0) == 0x46546c67 && u32(
bytes, 4) == 2,
"Expected GLB 2.0");
230 require(u32(
bytes, 8) ==
bytes.size(),
"Invalid GLB length");
233 "Invalid GLB JSON chunk");
235 require(json.valid(),
"Invalid GLB JSON");
236 auto root = json.root();
237 std::span<const std::byte> bin;
241 require(
n % 4 == 0 &&
n <=
bytes.size() -
at,
"Invalid GLB chunk");
242 if (
type == 0x004e4942) {
243 require(bin.empty(),
"Duplicate BIN chunk");
248 auto vrm =
root.get(
"extensions").get(
"VRMC_vrm");
249 if (!vrm || vrm.getString(
"specVersion") !=
"1.0")
255 require(
nodes.isArray(),
"Missing nodes");
256 d.parents.resize(
nodes.size(), -1);
257 for (
size_t i = 0; i <
nodes.size(); ++i) {
259 d.nodes.push_back(
node.getString(
"name"));
260 d.nodeMeshes.push_back(
node.has(
"mesh") ?
index(
node.get(
"mesh"), meshes.size()) : -1);
262 for (
size_t k = 0; k <
children.size(); ++k) {
264 require(
d.parents[child] == -1 && child !=
int(i),
"Invalid node hierarchy");
265 d.parents[child] = int(i);
268 for (
size_t i = 0; i <
nodes.size(); ++i) {
272 require(++
steps <=
nodes.size(),
"Cyclic node hierarchy");
276 auto humans = vrm.get(
"humanoid").get(
"humanBones");
277 require(humans.isObject(),
"Missing humanoid mapping");
278 for (
const auto&
name : humans.keys())
280 auto images =
root.get(
"images"), views =
root.get(
"bufferViews");
281 for (
size_t i = 0; i < images.size(); ++i) {
282 auto v = views.at(
index(images.at(i).get(
"bufferView"), views.size()));
283 require(
v.getInt(
"buffer", 0) == 0,
"VRM image must use GLB buffer");
284 auto start =
v.get(
"byteOffset").asInt64(0),
n =
v.get(
"byteLength").asInt64(-1);
285 require(
start >= 0 &&
n > 0 && uint64_t(
start) <= bin.size() && uint64_t(
n) <= bin.size() -
size_t(
start),
286 "Image buffer view is out of range");
287 auto image = bin.subspan(
size_t(
start),
size_t(
n));
291 auto skins =
root.get(
"skins"), accessors =
root.get(
"accessors");
292 for (
size_t i = 0; i < skins.size(); ++i) {
293 auto skin = skins.at(i);
294 if (!skin.has(
"inverseBindMatrices"))
297 auto accessor = accessors.at(
index(skin.get(
"inverseBindMatrices"), accessors.size()));
298 require(accessor.getString(
"type") ==
"MAT4" && accessor.getInt(
"componentType") == 5126,
299 "Invalid inverse bind matrix accessor");
300 require(accessor.getInt(
"count") >=
static_cast<int>(skin.get(
"joints").size()),
301 "Inverse bind matrix count does not cover joints");
302 for (
size_t j = 0; j < skin.get(
"joints").
size(); ++j) (
void)
index(skin.get(
"joints").at(j),
nodes.size());
304 readMaterials(
d,
root);
305 for (
size_t i = 0; i < meshes.size(); ++i) {
306 std::vector<int>
slots;
307 auto ps = meshes.at(i).get(
"primitives");
308 for (
size_t k = 0; k < ps.size(); ++k)
309 slots.push_back(ps.at(k).has(
"material") ?
index(ps.at(k).get(
"material"),
d.materials.size()) : -1);
310 d.meshMaterials.push_back(std::move(
slots));
312 readExpressions(
d, vrm,
root);
313 auto look = vrm.get(
"lookAt");
315 d.look.present =
true;
316 d.look.expression = choice(look.get(
"type"),
"bone", {
"bone",
"expression"}) ==
"expression";
317 d.look.offset = vector<3>(look.get(
"offsetFromHeadBone"), {});
319 auto x = look.get(
name);
320 r.input =
number(
x.get(
"inputMaxValue"), 90);
321 r.output =
number(
x.get(
"outputScale"), 10);
322 require(
r.input >= 0 &&
r.output >= 0,
"Negative gaze range");
324 range(
"rangeMapHorizontalInner",
d.look.inner);
325 range(
"rangeMapHorizontalOuter",
d.look.outer);
326 range(
"rangeMapVerticalDown",
d.look.down);
327 range(
"rangeMapVerticalUp",
d.look.up);
331 }
catch (
const Invalid& e) {
building::EdgeCurveGroup group
vk::UniqueSampler sampler
std::array< double, 10 > q
LocalPageCacheEntry slots[ShadowConfig::kLocalSlots]
std::map< std::string, std::span< const std::uint8_t > > members
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.
static Document parse(const std::string &text, std::string *error=nullptr)
Parse.
EVENGINE_API_FOUNDATION public API.
eve::Result< VrmDocument > parseVrm(std::span< const std::byte > bytes)
Validate a self-contained GLB's VRM extension before publishing runtime state.
Validated VRM schema data. Unknown optional fields are ignored; unknown versions fail.