mirror of
https://github.com/SlimeVR/SlimeVR-Server.git
synced 2026-04-06 02:01:58 +02:00
Update VRM reader to the new VMC 1.0 spec (#1481)
This commit is contained in:
@@ -70,11 +70,10 @@ export function VMCSettings() {
|
||||
);
|
||||
if (values.vmc.vrmJson !== undefined) {
|
||||
if (values.vmc.vrmJson.length > 0) {
|
||||
vmcOsc.vrmJson = await parseVRMFile(values.vmc.vrmJson[0]);
|
||||
if (vmcOsc.vrmJson) {
|
||||
setModelName(
|
||||
JSON.parse(vmcOsc.vrmJson)?.extensions?.VRM?.meta?.title || ''
|
||||
);
|
||||
const file = await parseVRMFile(values.vmc.vrmJson[0]);
|
||||
if (file) {
|
||||
vmcOsc.vrmJson = file.json;
|
||||
setModelName(file.name);
|
||||
}
|
||||
} else {
|
||||
vmcOsc.vrmJson = '';
|
||||
@@ -114,7 +113,7 @@ export function VMCSettings() {
|
||||
}
|
||||
const vrmJson = settings.vmcOsc.vrmJson?.toString();
|
||||
if (vrmJson) {
|
||||
setModelName(JSON.parse(vrmJson)?.extensions?.VRM?.meta?.title || '');
|
||||
setModelName(getVRMName(vrmJson) || '');
|
||||
}
|
||||
|
||||
formData.vmc.anchorHip = settings.vmcOsc.anchorHip;
|
||||
@@ -299,7 +298,9 @@ export function VMCSettings() {
|
||||
const gltfHeaderStart = 0;
|
||||
const gltfHeaderEnd = 20;
|
||||
|
||||
async function parseVRMFile(vrm: File): Promise<string | null> {
|
||||
async function parseVRMFile(
|
||||
vrm: File
|
||||
): Promise<{ json: string; name: string } | null> {
|
||||
const headerView = new DataView(
|
||||
await vrm.slice(gltfHeaderStart, gltfHeaderEnd).arrayBuffer()
|
||||
);
|
||||
@@ -337,7 +338,36 @@ async function parseVRMFile(vrm: File): Promise<string | null> {
|
||||
return null;
|
||||
}
|
||||
|
||||
return vrm
|
||||
const json = await vrm
|
||||
.slice(gltfHeaderEnd, gltfHeaderEnd + jsonLength, 'application/json')
|
||||
.text();
|
||||
|
||||
const name = getVRMName(json);
|
||||
if (name === null) return null;
|
||||
|
||||
return { json, name };
|
||||
}
|
||||
|
||||
function getVRMName(json: string): string | null {
|
||||
try {
|
||||
const data = JSON.parse(json);
|
||||
|
||||
if (typeof data?.extensions?.VRMC_vrm?.specVersion === 'string') {
|
||||
const name = data.extensions.VRMC_vrm.meta.name;
|
||||
|
||||
if (typeof name !== 'string') {
|
||||
error(
|
||||
`The name of the VRM model is not a string, instead it is a ${typeof name}`
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
return name;
|
||||
} else {
|
||||
return data?.extensions?.VRM?.meta?.title || '';
|
||||
}
|
||||
} catch (e) {
|
||||
error(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,72 +2,184 @@ package dev.slimevr.osc
|
||||
|
||||
import dev.slimevr.tracking.processor.BoneType
|
||||
import dev.slimevr.tracking.trackers.TrackerPosition
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Unity HumanBodyBones from:
|
||||
* https://docs.unity3d.com/ScriptReference/HumanBodyBones.html
|
||||
*/
|
||||
@Serializable
|
||||
enum class UnityBone(
|
||||
val stringVal: String,
|
||||
val boneType: BoneType?,
|
||||
val trackerPosition: TrackerPosition?,
|
||||
) {
|
||||
|
||||
@SerialName("hips")
|
||||
HIPS("Hips", BoneType.HIP, TrackerPosition.HIP),
|
||||
|
||||
@SerialName("leftUpperLeg")
|
||||
LEFT_UPPER_LEG("LeftUpperLeg", BoneType.LEFT_UPPER_LEG, TrackerPosition.LEFT_UPPER_LEG),
|
||||
|
||||
@SerialName("rightUpperLeg")
|
||||
RIGHT_UPPER_LEG("RightUpperLeg", BoneType.RIGHT_UPPER_LEG, TrackerPosition.RIGHT_UPPER_LEG),
|
||||
|
||||
@SerialName("leftLowerLeg")
|
||||
LEFT_LOWER_LEG("LeftLowerLeg", BoneType.LEFT_LOWER_LEG, TrackerPosition.LEFT_LOWER_LEG),
|
||||
|
||||
@SerialName("rightLowerLeg")
|
||||
RIGHT_LOWER_LEG("RightLowerLeg", BoneType.RIGHT_LOWER_LEG, TrackerPosition.RIGHT_LOWER_LEG),
|
||||
|
||||
@SerialName("leftFoot")
|
||||
LEFT_FOOT("LeftFoot", BoneType.LEFT_FOOT, TrackerPosition.LEFT_FOOT),
|
||||
|
||||
@SerialName("rightFoot")
|
||||
RIGHT_FOOT("RightFoot", BoneType.RIGHT_FOOT, TrackerPosition.RIGHT_FOOT),
|
||||
|
||||
@SerialName("spine")
|
||||
SPINE("Spine", BoneType.WAIST, TrackerPosition.WAIST),
|
||||
|
||||
@SerialName("chest")
|
||||
CHEST("Chest", BoneType.CHEST, TrackerPosition.CHEST),
|
||||
|
||||
@SerialName("upperChest")
|
||||
UPPER_CHEST("UpperChest", BoneType.CHEST, TrackerPosition.CHEST),
|
||||
|
||||
@SerialName("neck")
|
||||
NECK("Neck", BoneType.NECK, TrackerPosition.NECK),
|
||||
|
||||
@SerialName("head")
|
||||
HEAD("Head", BoneType.HEAD, TrackerPosition.HEAD),
|
||||
|
||||
@SerialName("leftShoulder")
|
||||
LEFT_SHOULDER("LeftShoulder", BoneType.LEFT_SHOULDER, TrackerPosition.LEFT_SHOULDER),
|
||||
|
||||
@SerialName("rightShoulder")
|
||||
RIGHT_SHOULDER("RightShoulder", BoneType.RIGHT_SHOULDER, TrackerPosition.RIGHT_SHOULDER),
|
||||
|
||||
@SerialName("leftUpperArm")
|
||||
LEFT_UPPER_ARM("LeftUpperArm", BoneType.LEFT_UPPER_ARM, TrackerPosition.LEFT_UPPER_ARM),
|
||||
|
||||
@SerialName("rightUpperArm")
|
||||
RIGHT_UPPER_ARM("RightUpperArm", BoneType.RIGHT_UPPER_ARM, TrackerPosition.RIGHT_UPPER_ARM),
|
||||
|
||||
@SerialName("leftLowerArm")
|
||||
LEFT_LOWER_ARM("LeftLowerArm", BoneType.LEFT_LOWER_ARM, TrackerPosition.LEFT_LOWER_ARM),
|
||||
|
||||
@SerialName("rightLowerArm")
|
||||
RIGHT_LOWER_ARM("RightLowerArm", BoneType.RIGHT_LOWER_ARM, TrackerPosition.RIGHT_LOWER_ARM),
|
||||
|
||||
@SerialName("leftHand")
|
||||
LEFT_HAND("LeftHand", BoneType.LEFT_HAND, TrackerPosition.LEFT_HAND),
|
||||
|
||||
@SerialName("rightHand")
|
||||
RIGHT_HAND("RightHand", BoneType.RIGHT_HAND, TrackerPosition.RIGHT_HAND),
|
||||
|
||||
@SerialName("leftToes")
|
||||
LEFT_TOES("LeftToes", null, null),
|
||||
|
||||
@SerialName("rightToes")
|
||||
RIGHT_TOES("RightToes", null, null),
|
||||
|
||||
@SerialName("leftEye")
|
||||
LEFT_EYE("LeftEye", null, null),
|
||||
|
||||
@SerialName("rightEye")
|
||||
RIGHT_EYE("RightEye", null, null),
|
||||
|
||||
@SerialName("jaw")
|
||||
JAW("Jaw", null, null),
|
||||
|
||||
@SerialName("leftThumbMetacarpal")
|
||||
LEFT_THUMB_PROXIMAL("LeftThumbProximal", BoneType.LEFT_THUMB_METACARPAL, TrackerPosition.LEFT_THUMB_METACARPAL),
|
||||
|
||||
@SerialName("leftThumbProximal")
|
||||
LEFT_THUMB_INTERMEDIATE("LeftThumbIntermediate", BoneType.LEFT_THUMB_PROXIMAL, TrackerPosition.LEFT_THUMB_PROXIMAL),
|
||||
|
||||
@SerialName("leftThumbDistal")
|
||||
LEFT_THUMB_DISTAL("LeftThumbDistal", BoneType.LEFT_THUMB_DISTAL, TrackerPosition.LEFT_THUMB_DISTAL),
|
||||
|
||||
@SerialName("leftIndexProximal")
|
||||
LEFT_INDEX_PROXIMAL("LeftIndexProximal", BoneType.LEFT_INDEX_PROXIMAL, TrackerPosition.LEFT_INDEX_PROXIMAL),
|
||||
|
||||
@SerialName("leftIndexIntermediate")
|
||||
LEFT_INDEX_INTERMEDIATE("LeftIndexIntermediate", BoneType.LEFT_INDEX_INTERMEDIATE, TrackerPosition.LEFT_INDEX_INTERMEDIATE),
|
||||
|
||||
@SerialName("leftIndexDistal")
|
||||
LEFT_INDEX_DISTAL("LeftIndexDistal", BoneType.LEFT_INDEX_DISTAL, TrackerPosition.LEFT_INDEX_DISTAL),
|
||||
|
||||
@SerialName("leftMiddleProximal")
|
||||
LEFT_MIDDLE_PROXIMAL("LeftMiddleProximal", BoneType.LEFT_MIDDLE_PROXIMAL, TrackerPosition.LEFT_MIDDLE_PROXIMAL),
|
||||
|
||||
@SerialName("leftMiddleIntermediate")
|
||||
LEFT_MIDDLE_INTERMEDIATE("LeftMiddleIntermediate", BoneType.LEFT_MIDDLE_INTERMEDIATE, TrackerPosition.LEFT_MIDDLE_INTERMEDIATE),
|
||||
|
||||
@SerialName("leftMiddleDistal")
|
||||
LEFT_MIDDLE_DISTAL("LeftMiddleDistal", BoneType.LEFT_MIDDLE_DISTAL, TrackerPosition.LEFT_MIDDLE_DISTAL),
|
||||
|
||||
@SerialName("leftRingProximal")
|
||||
LEFT_RING_PROXIMAL("LeftRingProximal", BoneType.LEFT_RING_PROXIMAL, TrackerPosition.LEFT_RING_PROXIMAL),
|
||||
|
||||
@SerialName("leftRingIntermediate")
|
||||
LEFT_RING_INTERMEDIATE("LeftRingIntermediate", BoneType.LEFT_RING_INTERMEDIATE, TrackerPosition.LEFT_RING_INTERMEDIATE),
|
||||
|
||||
@SerialName("leftRingDistal")
|
||||
LEFT_RING_DISTAL("LeftRingDistal", BoneType.LEFT_RING_DISTAL, TrackerPosition.LEFT_RING_DISTAL),
|
||||
|
||||
@SerialName("leftLittleProximal")
|
||||
LEFT_LITTLE_PROXIMAL("LeftLittleProximal", BoneType.LEFT_LITTLE_PROXIMAL, TrackerPosition.LEFT_LITTLE_PROXIMAL),
|
||||
|
||||
@SerialName("leftLittleIntermediate")
|
||||
LEFT_LITTLE_INTERMEDIATE("LeftLittleIntermediate", BoneType.LEFT_LITTLE_INTERMEDIATE, TrackerPosition.LEFT_LITTLE_INTERMEDIATE),
|
||||
|
||||
@SerialName("leftLittleDistal")
|
||||
LEFT_LITTLE_DISTAL("LeftLittleDistal", BoneType.LEFT_LITTLE_DISTAL, TrackerPosition.LEFT_LITTLE_DISTAL),
|
||||
|
||||
@SerialName("rightThumbMetacarpal")
|
||||
RIGHT_THUMB_PROXIMAL("RightThumbProximal", BoneType.RIGHT_THUMB_METACARPAL, TrackerPosition.RIGHT_THUMB_METACARPAL),
|
||||
|
||||
@SerialName("rightThumbProximal")
|
||||
RIGHT_THUMB_INTERMEDIATE("RightThumbIntermediate", BoneType.RIGHT_THUMB_PROXIMAL, TrackerPosition.RIGHT_THUMB_PROXIMAL),
|
||||
|
||||
@SerialName("rightThumbDistal")
|
||||
RIGHT_THUMB_DISTAL("RightThumbDistal", BoneType.RIGHT_THUMB_DISTAL, TrackerPosition.RIGHT_THUMB_DISTAL),
|
||||
|
||||
@SerialName("rightIndexProximal")
|
||||
RIGHT_INDEX_PROXIMAL("RightIndexProximal", BoneType.RIGHT_INDEX_PROXIMAL, TrackerPosition.RIGHT_INDEX_PROXIMAL),
|
||||
|
||||
@SerialName("rightIndexIntermediate")
|
||||
RIGHT_INDEX_INTERMEDIATE("RightIndexIntermediate", BoneType.RIGHT_INDEX_INTERMEDIATE, TrackerPosition.RIGHT_INDEX_INTERMEDIATE),
|
||||
|
||||
@SerialName("rightIndexDistal")
|
||||
RIGHT_INDEX_DISTAL("RightIndexDistal", BoneType.RIGHT_INDEX_DISTAL, TrackerPosition.RIGHT_INDEX_DISTAL),
|
||||
|
||||
@SerialName("rightMiddleProximal")
|
||||
RIGHT_MIDDLE_PROXIMAL("RightMiddleProximal", BoneType.RIGHT_MIDDLE_PROXIMAL, TrackerPosition.RIGHT_MIDDLE_PROXIMAL),
|
||||
|
||||
@SerialName("rightMiddleIntermediate")
|
||||
RIGHT_MIDDLE_INTERMEDIATE("RightMiddleIntermediate", BoneType.RIGHT_MIDDLE_INTERMEDIATE, TrackerPosition.RIGHT_MIDDLE_INTERMEDIATE),
|
||||
|
||||
@SerialName("rightMiddleDistal")
|
||||
RIGHT_MIDDLE_DISTAL("RightMiddleDistal", BoneType.RIGHT_MIDDLE_DISTAL, TrackerPosition.RIGHT_MIDDLE_DISTAL),
|
||||
|
||||
@SerialName("rightRingProximal")
|
||||
RIGHT_RING_PROXIMAL("RightRingProximal", BoneType.RIGHT_RING_PROXIMAL, TrackerPosition.RIGHT_RING_PROXIMAL),
|
||||
|
||||
@SerialName("rightRingIntermediate")
|
||||
RIGHT_RING_INTERMEDIATE("RightRingIntermediate", BoneType.RIGHT_RING_INTERMEDIATE, TrackerPosition.RIGHT_RING_INTERMEDIATE),
|
||||
|
||||
@SerialName("rightRingDistal")
|
||||
RIGHT_RING_DISTAL("RightRingDistal", BoneType.RIGHT_RING_DISTAL, TrackerPosition.RIGHT_RING_DISTAL),
|
||||
|
||||
@SerialName("rightLittleProximal")
|
||||
RIGHT_LITTLE_PROXIMAL("RightLittleProximal", BoneType.RIGHT_LITTLE_PROXIMAL, TrackerPosition.RIGHT_LITTLE_PROXIMAL),
|
||||
|
||||
@SerialName("rightLittleIntermediate")
|
||||
RIGHT_LITTLE_INTERMEDIATE("RightLittleIntermediate", BoneType.RIGHT_LITTLE_INTERMEDIATE, TrackerPosition.RIGHT_LITTLE_INTERMEDIATE),
|
||||
|
||||
@SerialName("rightLittleDistal")
|
||||
RIGHT_LITTLE_DISTAL("RightLittleDistal", BoneType.RIGHT_LITTLE_DISTAL, TrackerPosition.RIGHT_LITTLE_DISTAL),
|
||||
|
||||
LAST_BONE("LastBone", null, null),
|
||||
;
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import io.eiren.util.logging.LogManager
|
||||
import io.github.axisangles.ktmath.Vector3
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.util.*
|
||||
|
||||
@@ -14,14 +13,23 @@ class VRMReader(vrmJson: String) {
|
||||
private val data: GLTF = jsonIgnoreKeys.decodeFromString(vrmJson)
|
||||
|
||||
fun getOffsetForBone(unityBone: UnityBone): Vector3 {
|
||||
val bone = try {
|
||||
data.extensions.vrm.humanoid.humanBones.first { it.bone.equals(unityBone.stringVal, ignoreCase = true) }
|
||||
} catch (e: NoSuchElementException) {
|
||||
val node = try {
|
||||
if (data.extensions.vrmV1 != null) {
|
||||
if (data.extensions.vrmV1.specVersion != "1.0") {
|
||||
LogManager.warning("[VRMReader] VRM version is not 1.0")
|
||||
}
|
||||
data.extensions.vrmV1.humanoid.humanBones.getValue(unityBone).node
|
||||
} else {
|
||||
data.extensions.vrmV0?.humanoid?.humanBones?.first {
|
||||
it.bone.equals(unityBone.stringVal, ignoreCase = true)
|
||||
}?.node
|
||||
}
|
||||
} catch (_: NoSuchElementException) {
|
||||
LogManager.warning("[VRMReader] Bone ${unityBone.stringVal} not found in JSON")
|
||||
return Vector3.NULL
|
||||
}
|
||||
null
|
||||
} ?: return Vector3.NULL
|
||||
|
||||
val translationNode = data.nodes[bone.node].translation ?: return Vector3.NULL
|
||||
val translationNode = data.nodes[node].translation ?: return Vector3.NULL
|
||||
|
||||
return Vector3(translationNode[0].toFloat(), translationNode[1].toFloat(), translationNode[2].toFloat())
|
||||
}
|
||||
@@ -37,17 +45,35 @@ data class GLTF(
|
||||
@Serializable
|
||||
data class Extensions(
|
||||
@SerialName("VRM")
|
||||
val vrm: VRM,
|
||||
val vrmV0: VRMV0? = null,
|
||||
@SerialName("VRMC_vrm")
|
||||
val vrmV1: VRMV1? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class VRM(
|
||||
val humanoid: Humanoid,
|
||||
data class VRMV1(
|
||||
val specVersion: String,
|
||||
val humanoid: HumanoidV1,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class Humanoid(
|
||||
val humanBones: List<HumanBone>,
|
||||
data class HumanoidV1(
|
||||
val humanBones: Map<UnityBone, HumanBoneV1>,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class HumanBoneV1(
|
||||
val node: Int,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class VRMV0(
|
||||
val humanoid: HumanoidV0,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class HumanoidV0(
|
||||
val humanBones: List<HumanBoneV0>,
|
||||
val armStretch: Double,
|
||||
val legStretch: Double,
|
||||
val upperArmTwist: Double,
|
||||
@@ -59,7 +85,7 @@ data class Humanoid(
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class HumanBone(
|
||||
data class HumanBoneV0(
|
||||
val bone: String,
|
||||
val node: Int,
|
||||
val useDefaultValues: Boolean,
|
||||
|
||||
Reference in New Issue
Block a user