载入中...
搜索中...
未找到
VrmDocument.cpp
浏览该文件的文档.
2#include <cmath>
3#include <cstdint>
4#include <limits>
5#include <stdexcept>
6#include "common/Json.h"
7
8namespace eve::avatar {
9namespace {
10using J = eve::json::Value;
11struct Invalid : std::runtime_error {
12 using std::runtime_error::runtime_error;
13};
14void require(bool value, const char* message) {
15 if (!value) throw Invalid(message);
16}
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;
21}
22int index(J value, size_t size) {
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);
27}
28float number(J value, float fallback) {
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();
34}
35template <size_t N>
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);
40 return fallback;
41}
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");
45 auto s = value.asString();
46 for (auto x : allowed)
47 if (s == x) return s;
48 throw Invalid("Unknown enum value: " + s);
49}
50VrmTexture texture(J info, J root) {
51 VrmTexture t;
52 if (!info) return t;
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);
61 }
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");
67 return t;
68}
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");
73 VrmMaterial m;
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);
86 if (mt) {
87 require(mt.getString("specVersion") == "1.0", "Unknown MToon version");
88 m.mtoon = true;
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);
101 m.outlineMode =
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);
115 }
116 d.materials.push_back(std::move(m));
117 }
118}
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());
125 VrmExpression e;
126 e.name = name;
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);
147 }
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);
157 }
158 binds = j.get("textureTransformBinds");
159 for (size_t i = 0; i < binds.size(); ++i) {
160 auto b = binds.at(i);
161 VrmExpression::Uv u;
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);
165 e.uvs.push_back(u);
166 }
167 d.expressions.push_back(std::move(e));
168 }
169 }
170}
171void readSpring(VrmDocument& d, J root) {
172 auto s = root.get("extensions").get("VRMC_springBone");
173 if (!s) return;
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) {
177 auto j = cs.at(i);
178 VrmCollider c;
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);
189 }
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);
193 VrmSpring spring;
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);
200 VrmSpring::Joint q;
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");
209 if (n) {
210 int parent = d.parents[q.node];
211 while (parent >= 0 && parent != spring.joints.back().node) parent = d.parents[parent];
212 require(parent >= 0, "Spring joints must follow the node hierarchy");
213 }
214 spring.joints.push_back(q);
215 }
216 auto memberships = j.get("colliderGroups");
217 for (size_t g = 0; g < memberships.size(); ++g) {
218 auto members = groups.at(index(memberships.at(g), groups.size())).get("colliders");
219 for (size_t c = 0; c < members.size(); ++c)
220 spring.colliders.push_back(index(members.at(c), d.colliders.size()));
221 }
222 d.springs.push_back(std::move(spring));
223 }
224}
225} // namespace
226
227eve::Result<VrmDocument> parseVrm(std::span<const std::byte> bytes) {
228 try {
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");
231 const size_t length = u32(bytes, 12);
232 require(u32(bytes, 16) == 0x4e4f534a && length % 4 == 0 && length <= bytes.size() - 20,
233 "Invalid GLB JSON chunk");
234 auto json = eve::json::Document::parse(std::string(reinterpret_cast<const char*>(bytes.data() + 20), length));
235 require(json.valid(), "Invalid GLB JSON");
236 auto root = json.root();
237 std::span<const std::byte> bin;
238 for (size_t at = 20 + length; at < bytes.size();) {
239 const auto n = u32(bytes, at), type = u32(bytes, at + 4);
240 at += 8;
241 require(n % 4 == 0 && n <= bytes.size() - at, "Invalid GLB chunk");
242 if (type == 0x004e4942) {
243 require(bin.empty(), "Duplicate BIN chunk");
244 bin = bytes.subspan(at, n);
245 }
246 at += n;
247 }
248 auto vrm = root.get("extensions").get("VRMC_vrm");
249 if (!vrm || vrm.getString("specVersion") != "1.0")
251 eve::DiagnosticCode::UnknownVersion, "Expected VRMC_vrm 1.0", "extensions.VRMC_vrm.specVersion"));
253 d.version = "1.0";
254 auto nodes = root.get("nodes"), meshes = root.get("meshes");
255 require(nodes.isArray(), "Missing nodes");
256 d.parents.resize(nodes.size(), -1);
257 for (size_t i = 0; i < nodes.size(); ++i) {
258 auto node = nodes.at(i);
259 d.nodes.push_back(node.getString("name"));
260 d.nodeMeshes.push_back(node.has("mesh") ? index(node.get("mesh"), meshes.size()) : -1);
261 auto children = node.get("children");
262 for (size_t k = 0; k < children.size(); ++k) {
263 int child = index(children.at(k), nodes.size());
264 require(d.parents[child] == -1 && child != int(i), "Invalid node hierarchy");
265 d.parents[child] = int(i);
266 }
267 }
268 for (size_t i = 0; i < nodes.size(); ++i) {
269 int p = int(i);
270 size_t steps = 0;
271 while (p >= 0) {
272 require(++steps <= nodes.size(), "Cyclic node hierarchy");
273 p = d.parents[p];
274 }
275 }
276 auto humans = vrm.get("humanoid").get("humanBones");
277 require(humans.isObject(), "Missing humanoid mapping");
278 for (const auto& name : humans.keys())
279 d.humanoid[name] = index(humans.get(name.c_str()).get("node"), nodes.size());
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));
288 d.images.emplace_back(image.begin(), image.end());
289 }
290 // The decoder requires an explicit palette accessor; reject before entering it.
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"))
296 eve::DiagnosticCode::Unsupported, "Skin requires explicit inverseBindMatrices", "skins"));
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());
303 }
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));
311 }
312 readExpressions(d, vrm, root);
313 auto look = vrm.get("lookAt");
314 if (look) {
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"), {});
318 auto range = [&](const char* name, VrmLookAt::Range& r) {
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");
323 };
324 range("rangeMapHorizontalInner", d.look.inner);
325 range("rangeMapHorizontalOuter", d.look.outer);
326 range("rangeMapVerticalDown", d.look.down);
327 range("rangeMapVerticalUp", d.look.up);
328 }
329 readSpring(d, root);
330 return eve::Result<VrmDocument>::success(std::move(d));
331 } catch (const Invalid& e) {
333 eve::Diagnostic::error(eve::DiagnosticCode::ParseError, e.what(), "vrm", {}, "avatar.vrm"));
334 }
335}
336} // namespace eve::avatar
double value
Duration start
float x
Definition AnimClip.cpp:738
int root
Definition AnimSmr.cpp:119
const std::string & s
building::EdgeCurveGroup group
float length
Definition CaveMesh.cpp:94
glm::vec4 p[6]
std::string message
tensor::Graph g
Definition GpuGraph.cpp:7
vk::UniqueSampler sampler
ShaderImageInput shape
vk::UniqueImage image
float u
Definition Grass.cpp:233
glm::vec3 n
Definition Grass.cpp:63
std::array< double, 10 > q
double r
float v
std::int32_t c
Range range
std::int32_t parent
std::uint64_t bytes
std::string name
MeleePoint3 b
Definition MeleeHit.cpp:41
std::vector< BvhNode > nodes
uint32_t groups
Definition OnnxGpgpu.cpp:39
float d
int steps
float t
Mesh * mesh
const RoadNode * node
double number
LocalPageCacheEntry slots[ShadowConfig::kLocalSlots]
int children
Definition TreeMesh.cpp:295
float size
Definition TreeMesh.cpp:156
std::map< std::string, std::span< const std::uint8_t > > members
uint32_t index
std::size_t at
float m[16]
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.
Definition Diagnostic.h:125
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
static Document parse(const std::string &text, std::string *error=nullptr)
Parse.
Definition Json.cpp:581
EVENGINE_API_FOUNDATION public API.
Definition Json.h:38
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.
Definition VrmDocument.h:88
glm::uvec4 info