/*
wearechat - generic multiplayer game with voip
Copyright (C) 2025 metamuffin
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, version 3 of the License only.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
*/
use anyhow::Result;
use gltf::Gltf;
use serde::Deserialize;
use std::collections::{BTreeMap, BTreeSet};
use weareshared::Vec3A;
#[derive(Debug, Default)]
pub struct VrmInfo {
pub bone_node_names: Vec<(usize, String)>,
pub hide_first_person: BTreeSet,
pub camera_mount: Option,
pub camera_mount_offset: Option,
}
pub fn extract_vrm_data(gltf: &Gltf) -> Result {
let mut o = VrmInfo::default();
let mut fp = None;
if let Some(vrm) = gltf.extension_value("VRM") {
// serde_json::to_writer(std::fs::File::create("/tmp/vrm").unwrap(), vrm).unwrap();
let vrm: Vrm = serde_json::from_value(vrm.clone())?;
for bone in vrm.humanoid.human_bones {
o.bone_node_names.push((bone.node, bone.bone))
}
fp = vrm.first_person;
}
if let Some(vrm) = gltf.extension_value("VRMC_vrm") {
// serde_json::to_writer(std::fs::File::create("/tmp/vrmc").unwrap(), vrm).unwrap();
let vrm: Vrmc = serde_json::from_value(vrm.clone())?;
for (name, bone) in vrm.humanoid.human_bones {
o.bone_node_names.push((bone.node, name))
}
fp = vrm.first_person;
}
if let Some(fp) = fp {
o.camera_mount = fp.first_person_bone;
o.camera_mount_offset = fp.first_person_bone_offset.map(convert_vrm_vec);
for ann in fp.mesh_annotations {
if let FirstPersonFlag::ThirdPersonOnly = ann.first_person_flag {
o.hide_first_person.insert(ann.node);
}
}
}
Ok(o)
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct Vrm {
humanoid: VrmHumanoid,
first_person: Option,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct VrmHumanoid {
human_bones: Vec,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct VrmHumanBone {
bone: String,
node: usize,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct Vrmc {
humanoid: VrmcHumanoid,
first_person: Option,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct VrmcFirstPerson {
first_person_bone: Option,
first_person_bone_offset: Option,
mesh_annotations: Vec,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct VrmcFirstPersonMeshAnnotation {
node: usize,
#[serde(alias = "type")] // spec says something but their sample uses "type" instead
first_person_flag: FirstPersonFlag,
}
#[derive(Debug, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
enum FirstPersonFlag {
ThirdPersonOnly,
FirstPersonOnly,
Both,
#[default]
Auto,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct VrmcHumanoid {
human_bones: BTreeMap,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct VrmcHumanBone {
node: usize,
}
#[derive(Debug, Deserialize)]
struct VrmVec3 {
x: f32,
y: f32,
z: f32,
}
fn convert_vrm_vec(VrmVec3 { x, y, z }: VrmVec3) -> Vec3A {
Vec3A::new(x, y, z)
}