aethex-studio/src/lib/avatar-platforms.ts
Claude 96163c8256
feat: Add AeThex cross-platform avatar toolkit
Implement comprehensive avatar import/export/rigging system supporting:
- Roblox (R6, R15, Rthro), VRChat, RecRoom, Spatial, Sandbox
- Decentraland, Meta Horizon, NeosVR, Resonite, ChilloutVR
- Universal AeThex format for lossless cross-platform conversion

Features:
- Platform-specific skeleton specs and bone mappings
- Auto-rig detection and universal bone name resolution
- Format handlers for GLB, GLTF, FBX, VRM, OBJ, PMX
- Validation against platform constraints (polygons, bones, textures)
- Avatar templates and optimization presets
- Compatibility scoring between platforms
2026-01-23 22:09:25 +00:00

548 lines
20 KiB
TypeScript

/**
* AeThex Avatar Platform Configuration
* Cross-platform avatar specifications for Roblox, VRChat, RecRoom, Spatial, Sandbox, and more
*/
export type AvatarPlatformId =
| 'roblox'
| 'vrchat'
| 'recroom'
| 'spatial'
| 'sandbox'
| 'neos'
| 'resonite'
| 'chilloutvr'
| 'decentraland'
| 'meta-horizon'
| 'universal';
export interface BoneMapping {
name: string;
universalName: string;
required: boolean;
parent?: string;
alternateNames?: string[];
}
export interface AvatarConstraints {
maxPolygons: number;
maxBones: number;
maxMaterials: number;
maxTextureSize: number;
maxFileSize: number; // in MB
supportedFormats: string[];
minHeight?: number;
maxHeight?: number;
requiresPhysBones?: boolean;
requiresDynamicBones?: boolean;
}
export interface SkeletonSpec {
type: 'humanoid' | 'generic' | 'r6' | 'r15' | 'rthro' | 'custom';
rootBone: string;
bones: BoneMapping[];
blendShapeSupport: boolean;
maxBlendShapes?: number;
ikSupport: boolean;
fingerTracking: boolean;
eyeTracking: boolean;
fullBodyTracking: boolean;
}
export interface AvatarPlatform {
id: AvatarPlatformId;
name: string;
displayName: string;
description: string;
color: string;
icon: string;
constraints: AvatarConstraints;
skeleton: SkeletonSpec;
exportFormat: string;
importFormats: string[];
documentation: string;
status: 'supported' | 'beta' | 'experimental' | 'coming-soon';
features: string[];
}
// Universal humanoid bone names (industry standard)
export const UNIVERSAL_BONES = {
// Root
ROOT: 'Root',
HIPS: 'Hips',
// Spine
SPINE: 'Spine',
SPINE1: 'Spine1',
SPINE2: 'Spine2',
CHEST: 'Chest',
UPPER_CHEST: 'UpperChest',
NECK: 'Neck',
HEAD: 'Head',
// Left Arm
LEFT_SHOULDER: 'LeftShoulder',
LEFT_UPPER_ARM: 'LeftUpperArm',
LEFT_LOWER_ARM: 'LeftLowerArm',
LEFT_HAND: 'LeftHand',
// Left Fingers
LEFT_THUMB_PROXIMAL: 'LeftThumbProximal',
LEFT_THUMB_INTERMEDIATE: 'LeftThumbIntermediate',
LEFT_THUMB_DISTAL: 'LeftThumbDistal',
LEFT_INDEX_PROXIMAL: 'LeftIndexProximal',
LEFT_INDEX_INTERMEDIATE: 'LeftIndexIntermediate',
LEFT_INDEX_DISTAL: 'LeftIndexDistal',
LEFT_MIDDLE_PROXIMAL: 'LeftMiddleProximal',
LEFT_MIDDLE_INTERMEDIATE: 'LeftMiddleIntermediate',
LEFT_MIDDLE_DISTAL: 'LeftMiddleDistal',
LEFT_RING_PROXIMAL: 'LeftRingProximal',
LEFT_RING_INTERMEDIATE: 'LeftRingIntermediate',
LEFT_RING_DISTAL: 'LeftRingDistal',
LEFT_LITTLE_PROXIMAL: 'LeftLittleProximal',
LEFT_LITTLE_INTERMEDIATE: 'LeftLittleIntermediate',
LEFT_LITTLE_DISTAL: 'LeftLittleDistal',
// Right Arm
RIGHT_SHOULDER: 'RightShoulder',
RIGHT_UPPER_ARM: 'RightUpperArm',
RIGHT_LOWER_ARM: 'RightLowerArm',
RIGHT_HAND: 'RightHand',
// Right Fingers
RIGHT_THUMB_PROXIMAL: 'RightThumbProximal',
RIGHT_THUMB_INTERMEDIATE: 'RightThumbIntermediate',
RIGHT_THUMB_DISTAL: 'RightThumbDistal',
RIGHT_INDEX_PROXIMAL: 'RightIndexProximal',
RIGHT_INDEX_INTERMEDIATE: 'RightIndexIntermediate',
RIGHT_INDEX_DISTAL: 'RightIndexDistal',
RIGHT_MIDDLE_PROXIMAL: 'RightMiddleProximal',
RIGHT_MIDDLE_INTERMEDIATE: 'RightMiddleIntermediate',
RIGHT_MIDDLE_DISTAL: 'RightMiddleDistal',
RIGHT_RING_PROXIMAL: 'RightRingProximal',
RIGHT_RING_INTERMEDIATE: 'RightRingIntermediate',
RIGHT_RING_DISTAL: 'RightRingDistal',
RIGHT_LITTLE_PROXIMAL: 'RightLittleProximal',
RIGHT_LITTLE_INTERMEDIATE: 'RightLittleIntermediate',
RIGHT_LITTLE_DISTAL: 'RightLittleDistal',
// Left Leg
LEFT_UPPER_LEG: 'LeftUpperLeg',
LEFT_LOWER_LEG: 'LeftLowerLeg',
LEFT_FOOT: 'LeftFoot',
LEFT_TOES: 'LeftToes',
// Right Leg
RIGHT_UPPER_LEG: 'RightUpperLeg',
RIGHT_LOWER_LEG: 'RightLowerLeg',
RIGHT_FOOT: 'RightFoot',
RIGHT_TOES: 'RightToes',
// Eyes
LEFT_EYE: 'LeftEye',
RIGHT_EYE: 'RightEye',
JAW: 'Jaw',
} as const;
// VRChat skeleton specification
const vrchatSkeleton: SkeletonSpec = {
type: 'humanoid',
rootBone: 'Armature',
blendShapeSupport: true,
maxBlendShapes: 256,
ikSupport: true,
fingerTracking: true,
eyeTracking: true,
fullBodyTracking: true,
bones: [
{ name: 'Hips', universalName: UNIVERSAL_BONES.HIPS, required: true },
{ name: 'Spine', universalName: UNIVERSAL_BONES.SPINE, required: true, parent: 'Hips' },
{ name: 'Chest', universalName: UNIVERSAL_BONES.CHEST, required: true, parent: 'Spine' },
{ name: 'Upper Chest', universalName: UNIVERSAL_BONES.UPPER_CHEST, required: false, parent: 'Chest' },
{ name: 'Neck', universalName: UNIVERSAL_BONES.NECK, required: true, parent: 'Chest' },
{ name: 'Head', universalName: UNIVERSAL_BONES.HEAD, required: true, parent: 'Neck' },
{ name: 'Left Shoulder', universalName: UNIVERSAL_BONES.LEFT_SHOULDER, required: false, parent: 'Chest' },
{ name: 'Left Upper Arm', universalName: UNIVERSAL_BONES.LEFT_UPPER_ARM, required: true, parent: 'Left Shoulder' },
{ name: 'Left Lower Arm', universalName: UNIVERSAL_BONES.LEFT_LOWER_ARM, required: true, parent: 'Left Upper Arm' },
{ name: 'Left Hand', universalName: UNIVERSAL_BONES.LEFT_HAND, required: true, parent: 'Left Lower Arm' },
{ name: 'Right Shoulder', universalName: UNIVERSAL_BONES.RIGHT_SHOULDER, required: false, parent: 'Chest' },
{ name: 'Right Upper Arm', universalName: UNIVERSAL_BONES.RIGHT_UPPER_ARM, required: true, parent: 'Right Shoulder' },
{ name: 'Right Lower Arm', universalName: UNIVERSAL_BONES.RIGHT_LOWER_ARM, required: true, parent: 'Right Upper Arm' },
{ name: 'Right Hand', universalName: UNIVERSAL_BONES.RIGHT_HAND, required: true, parent: 'Right Lower Arm' },
{ name: 'Left Upper Leg', universalName: UNIVERSAL_BONES.LEFT_UPPER_LEG, required: true, parent: 'Hips' },
{ name: 'Left Lower Leg', universalName: UNIVERSAL_BONES.LEFT_LOWER_LEG, required: true, parent: 'Left Upper Leg' },
{ name: 'Left Foot', universalName: UNIVERSAL_BONES.LEFT_FOOT, required: true, parent: 'Left Lower Leg' },
{ name: 'Left Toes', universalName: UNIVERSAL_BONES.LEFT_TOES, required: false, parent: 'Left Foot' },
{ name: 'Right Upper Leg', universalName: UNIVERSAL_BONES.RIGHT_UPPER_LEG, required: true, parent: 'Hips' },
{ name: 'Right Lower Leg', universalName: UNIVERSAL_BONES.RIGHT_LOWER_LEG, required: true, parent: 'Right Upper Leg' },
{ name: 'Right Foot', universalName: UNIVERSAL_BONES.RIGHT_FOOT, required: true, parent: 'Right Lower Leg' },
{ name: 'Right Toes', universalName: UNIVERSAL_BONES.RIGHT_TOES, required: false, parent: 'Right Foot' },
{ name: 'Left Eye', universalName: UNIVERSAL_BONES.LEFT_EYE, required: false, parent: 'Head' },
{ name: 'Right Eye', universalName: UNIVERSAL_BONES.RIGHT_EYE, required: false, parent: 'Head' },
{ name: 'Jaw', universalName: UNIVERSAL_BONES.JAW, required: false, parent: 'Head' },
],
};
// Roblox R15 skeleton specification
const robloxR15Skeleton: SkeletonSpec = {
type: 'r15',
rootBone: 'HumanoidRootPart',
blendShapeSupport: true,
maxBlendShapes: 50,
ikSupport: true,
fingerTracking: false,
eyeTracking: true,
fullBodyTracking: false,
bones: [
{ name: 'HumanoidRootPart', universalName: UNIVERSAL_BONES.ROOT, required: true },
{ name: 'LowerTorso', universalName: UNIVERSAL_BONES.HIPS, required: true, parent: 'HumanoidRootPart' },
{ name: 'UpperTorso', universalName: UNIVERSAL_BONES.SPINE, required: true, parent: 'LowerTorso' },
{ name: 'Head', universalName: UNIVERSAL_BONES.HEAD, required: true, parent: 'UpperTorso' },
{ name: 'LeftUpperArm', universalName: UNIVERSAL_BONES.LEFT_UPPER_ARM, required: true, parent: 'UpperTorso' },
{ name: 'LeftLowerArm', universalName: UNIVERSAL_BONES.LEFT_LOWER_ARM, required: true, parent: 'LeftUpperArm' },
{ name: 'LeftHand', universalName: UNIVERSAL_BONES.LEFT_HAND, required: true, parent: 'LeftLowerArm' },
{ name: 'RightUpperArm', universalName: UNIVERSAL_BONES.RIGHT_UPPER_ARM, required: true, parent: 'UpperTorso' },
{ name: 'RightLowerArm', universalName: UNIVERSAL_BONES.RIGHT_LOWER_ARM, required: true, parent: 'RightUpperArm' },
{ name: 'RightHand', universalName: UNIVERSAL_BONES.RIGHT_HAND, required: true, parent: 'RightLowerArm' },
{ name: 'LeftUpperLeg', universalName: UNIVERSAL_BONES.LEFT_UPPER_LEG, required: true, parent: 'LowerTorso' },
{ name: 'LeftLowerLeg', universalName: UNIVERSAL_BONES.LEFT_LOWER_LEG, required: true, parent: 'LeftUpperLeg' },
{ name: 'LeftFoot', universalName: UNIVERSAL_BONES.LEFT_FOOT, required: true, parent: 'LeftLowerLeg' },
{ name: 'RightUpperLeg', universalName: UNIVERSAL_BONES.RIGHT_UPPER_LEG, required: true, parent: 'LowerTorso' },
{ name: 'RightLowerLeg', universalName: UNIVERSAL_BONES.RIGHT_LOWER_LEG, required: true, parent: 'RightUpperLeg' },
{ name: 'RightFoot', universalName: UNIVERSAL_BONES.RIGHT_FOOT, required: true, parent: 'RightLowerLeg' },
],
};
// RecRoom skeleton specification
const recRoomSkeleton: SkeletonSpec = {
type: 'humanoid',
rootBone: 'Root',
blendShapeSupport: true,
maxBlendShapes: 30,
ikSupport: true,
fingerTracking: false,
eyeTracking: false,
fullBodyTracking: false,
bones: [
{ name: 'Root', universalName: UNIVERSAL_BONES.ROOT, required: true },
{ name: 'Hips', universalName: UNIVERSAL_BONES.HIPS, required: true, parent: 'Root' },
{ name: 'Spine', universalName: UNIVERSAL_BONES.SPINE, required: true, parent: 'Hips' },
{ name: 'Chest', universalName: UNIVERSAL_BONES.CHEST, required: true, parent: 'Spine' },
{ name: 'Neck', universalName: UNIVERSAL_BONES.NECK, required: true, parent: 'Chest' },
{ name: 'Head', universalName: UNIVERSAL_BONES.HEAD, required: true, parent: 'Neck' },
{ name: 'LeftArm', universalName: UNIVERSAL_BONES.LEFT_UPPER_ARM, required: true, parent: 'Chest' },
{ name: 'LeftForeArm', universalName: UNIVERSAL_BONES.LEFT_LOWER_ARM, required: true, parent: 'LeftArm' },
{ name: 'LeftHand', universalName: UNIVERSAL_BONES.LEFT_HAND, required: true, parent: 'LeftForeArm' },
{ name: 'RightArm', universalName: UNIVERSAL_BONES.RIGHT_UPPER_ARM, required: true, parent: 'Chest' },
{ name: 'RightForeArm', universalName: UNIVERSAL_BONES.RIGHT_LOWER_ARM, required: true, parent: 'RightArm' },
{ name: 'RightHand', universalName: UNIVERSAL_BONES.RIGHT_HAND, required: true, parent: 'RightForeArm' },
{ name: 'LeftLeg', universalName: UNIVERSAL_BONES.LEFT_UPPER_LEG, required: true, parent: 'Hips' },
{ name: 'LeftKnee', universalName: UNIVERSAL_BONES.LEFT_LOWER_LEG, required: true, parent: 'LeftLeg' },
{ name: 'LeftFoot', universalName: UNIVERSAL_BONES.LEFT_FOOT, required: true, parent: 'LeftKnee' },
{ name: 'RightLeg', universalName: UNIVERSAL_BONES.RIGHT_UPPER_LEG, required: true, parent: 'Hips' },
{ name: 'RightKnee', universalName: UNIVERSAL_BONES.RIGHT_LOWER_LEG, required: true, parent: 'RightLeg' },
{ name: 'RightFoot', universalName: UNIVERSAL_BONES.RIGHT_FOOT, required: true, parent: 'RightKnee' },
],
};
// Universal/Standard humanoid skeleton
const universalSkeleton: SkeletonSpec = {
type: 'humanoid',
rootBone: 'Armature',
blendShapeSupport: true,
maxBlendShapes: 256,
ikSupport: true,
fingerTracking: true,
eyeTracking: true,
fullBodyTracking: true,
bones: Object.entries(UNIVERSAL_BONES).map(([key, name]) => ({
name,
universalName: name,
required: ['HIPS', 'SPINE', 'HEAD', 'LEFT_UPPER_ARM', 'LEFT_LOWER_ARM', 'LEFT_HAND',
'RIGHT_UPPER_ARM', 'RIGHT_LOWER_ARM', 'RIGHT_HAND', 'LEFT_UPPER_LEG',
'LEFT_LOWER_LEG', 'LEFT_FOOT', 'RIGHT_UPPER_LEG', 'RIGHT_LOWER_LEG', 'RIGHT_FOOT'].includes(key),
})),
};
export const avatarPlatforms: Record<AvatarPlatformId, AvatarPlatform> = {
roblox: {
id: 'roblox',
name: 'Roblox',
displayName: 'Roblox Studio',
description: 'Import/export avatars for Roblox experiences with R6, R15, or Rthro support',
color: '#00A2FF',
icon: 'gamepad-2',
constraints: {
maxPolygons: 10000,
maxBones: 76,
maxMaterials: 1,
maxTextureSize: 1024,
maxFileSize: 30,
supportedFormats: ['fbx', 'obj'],
minHeight: 0.7,
maxHeight: 3.0,
},
skeleton: robloxR15Skeleton,
exportFormat: 'fbx',
importFormats: ['fbx', 'obj', 'glb', 'gltf', 'vrm'],
documentation: 'https://create.roblox.com/docs/art/characters',
status: 'supported',
features: ['R6', 'R15', 'Rthro', 'Layered Clothing', 'Dynamic Heads', 'Accessories'],
},
vrchat: {
id: 'vrchat',
name: 'VRChat',
displayName: 'VRChat SDK',
description: 'Create avatars for VRChat with full body tracking, PhysBones, and expressions',
color: '#1FB2A5',
icon: 'glasses',
constraints: {
maxPolygons: 70000, // Poor rating threshold
maxBones: 256,
maxMaterials: 32,
maxTextureSize: 2048,
maxFileSize: 200,
supportedFormats: ['fbx', 'vrm'],
requiresPhysBones: true,
},
skeleton: vrchatSkeleton,
exportFormat: 'unitypackage',
importFormats: ['fbx', 'glb', 'gltf', 'vrm', 'pmx'],
documentation: 'https://creators.vrchat.com/avatars/',
status: 'supported',
features: ['PhysBones', 'Avatar Dynamics', 'Eye Tracking', 'Face Tracking', 'OSC', 'Full Body'],
},
recroom: {
id: 'recroom',
name: 'RecRoom',
displayName: 'Rec Room',
description: 'Create fun, stylized avatars for Rec Room social experiences',
color: '#FF6B6B',
icon: 'party-popper',
constraints: {
maxPolygons: 15000,
maxBones: 52,
maxMaterials: 4,
maxTextureSize: 512,
maxFileSize: 20,
supportedFormats: ['fbx'],
},
skeleton: recRoomSkeleton,
exportFormat: 'fbx',
importFormats: ['fbx', 'glb', 'gltf', 'vrm'],
documentation: 'https://recroom.com/developer',
status: 'supported',
features: ['Stylized Look', 'Props', 'Costumes', 'Expressions'],
},
spatial: {
id: 'spatial',
name: 'Spatial',
displayName: 'Spatial Creator Toolkit',
description: 'Create avatars for Spatial VR/AR experiences and virtual spaces',
color: '#9B5DE5',
icon: 'globe',
constraints: {
maxPolygons: 50000,
maxBones: 128,
maxMaterials: 8,
maxTextureSize: 2048,
maxFileSize: 50,
supportedFormats: ['glb', 'gltf', 'vrm'],
},
skeleton: vrchatSkeleton,
exportFormat: 'glb',
importFormats: ['glb', 'gltf', 'vrm', 'fbx'],
documentation: 'https://toolkit.spatial.io/docs/avatars',
status: 'supported',
features: ['Ready Player Me', 'Custom Avatars', 'Emotes', 'Accessories'],
},
sandbox: {
id: 'sandbox',
name: 'Sandbox',
displayName: 'The Sandbox',
description: 'Create voxel-style or custom avatars for The Sandbox metaverse',
color: '#00D4FF',
icon: 'box',
constraints: {
maxPolygons: 20000,
maxBones: 64,
maxMaterials: 8,
maxTextureSize: 1024,
maxFileSize: 30,
supportedFormats: ['glb', 'gltf', 'vox'],
},
skeleton: universalSkeleton,
exportFormat: 'glb',
importFormats: ['glb', 'gltf', 'fbx', 'vrm', 'vox'],
documentation: 'https://sandboxgame.gitbook.io/the-sandbox',
status: 'supported',
features: ['Voxel Style', 'LAND Integration', 'NFT Support', 'Equipment'],
},
neos: {
id: 'neos',
name: 'NeosVR',
displayName: 'NeosVR',
description: 'Create highly customizable avatars for NeosVR',
color: '#F5A623',
icon: 'cpu',
constraints: {
maxPolygons: 100000,
maxBones: 256,
maxMaterials: 32,
maxTextureSize: 4096,
maxFileSize: 300,
supportedFormats: ['fbx', 'glb', 'gltf', 'vrm'],
},
skeleton: vrchatSkeleton,
exportFormat: 'glb',
importFormats: ['fbx', 'glb', 'gltf', 'vrm', 'obj'],
documentation: 'https://wiki.neos.com/',
status: 'beta',
features: ['LogiX', 'Dynamic Bones', 'Full Customization', 'In-World Editing'],
},
resonite: {
id: 'resonite',
name: 'Resonite',
displayName: 'Resonite',
description: 'Successor to NeosVR with enhanced avatar capabilities',
color: '#7B68EE',
icon: 'sparkles',
constraints: {
maxPolygons: 100000,
maxBones: 256,
maxMaterials: 32,
maxTextureSize: 4096,
maxFileSize: 300,
supportedFormats: ['fbx', 'glb', 'gltf', 'vrm'],
},
skeleton: vrchatSkeleton,
exportFormat: 'glb',
importFormats: ['fbx', 'glb', 'gltf', 'vrm', 'obj'],
documentation: 'https://wiki.resonite.com/',
status: 'beta',
features: ['ProtoFlux', 'Dynamic Bones', 'Face Tracking', 'Full Body'],
},
chilloutvr: {
id: 'chilloutvr',
name: 'ChilloutVR',
displayName: 'ChilloutVR',
description: 'Create avatars for ChilloutVR social platform',
color: '#E91E63',
icon: 'heart',
constraints: {
maxPolygons: 80000,
maxBones: 256,
maxMaterials: 24,
maxTextureSize: 2048,
maxFileSize: 150,
supportedFormats: ['fbx', 'vrm'],
},
skeleton: vrchatSkeleton,
exportFormat: 'unitypackage',
importFormats: ['fbx', 'glb', 'gltf', 'vrm'],
documentation: 'https://docs.abinteractive.net/',
status: 'beta',
features: ['Advanced Rigging', 'Toggles', 'Gestures', 'Eye/Face Tracking'],
},
decentraland: {
id: 'decentraland',
name: 'Decentraland',
displayName: 'Decentraland',
description: 'Create Web3-enabled avatars for the Decentraland metaverse',
color: '#FF2D55',
icon: 'landmark',
constraints: {
maxPolygons: 1500,
maxBones: 52,
maxMaterials: 2,
maxTextureSize: 512,
maxFileSize: 2,
supportedFormats: ['glb'],
},
skeleton: universalSkeleton,
exportFormat: 'glb',
importFormats: ['glb', 'gltf', 'fbx', 'vrm'],
documentation: 'https://docs.decentraland.org/creator/wearables/creating-wearables/',
status: 'supported',
features: ['Wearables', 'NFT Integration', 'Emotes', 'Blockchain'],
},
'meta-horizon': {
id: 'meta-horizon',
name: 'Meta Horizon',
displayName: 'Meta Horizon Worlds',
description: 'Create avatars for Meta Horizon Worlds VR platform',
color: '#0668E1',
icon: 'headphones',
constraints: {
maxPolygons: 25000,
maxBones: 70,
maxMaterials: 8,
maxTextureSize: 1024,
maxFileSize: 50,
supportedFormats: ['glb', 'fbx'],
},
skeleton: universalSkeleton,
exportFormat: 'glb',
importFormats: ['glb', 'gltf', 'fbx', 'vrm'],
documentation: 'https://developer.oculus.com/',
status: 'experimental',
features: ['Hand Tracking', 'Body Estimation', 'Expressions'],
},
universal: {
id: 'universal',
name: 'Universal',
displayName: 'AeThex Universal Format',
description: 'The AeThex universal avatar format compatible with all platforms',
color: '#00FF88',
icon: 'sparkles',
constraints: {
maxPolygons: 100000,
maxBones: 256,
maxMaterials: 32,
maxTextureSize: 4096,
maxFileSize: 500,
supportedFormats: ['aeth', 'glb', 'gltf', 'vrm', 'fbx'],
},
skeleton: universalSkeleton,
exportFormat: 'aeth',
importFormats: ['fbx', 'glb', 'gltf', 'vrm', 'obj', 'pmx', 'vroid'],
documentation: 'https://aethex.dev/docs/avatar-format',
status: 'supported',
features: ['All Platforms', 'Lossless Conversion', 'Metadata Preservation', 'Auto-Optimization'],
},
};
export const supportedPlatforms = Object.values(avatarPlatforms).filter(
(p) => p.status === 'supported' || p.status === 'beta'
);
export function getAvatarPlatform(id: AvatarPlatformId): AvatarPlatform {
return avatarPlatforms[id];
}
export function isPlatformSupported(id: AvatarPlatformId): boolean {
return avatarPlatforms[id].status === 'supported';
}
export function getConstraintsForPlatform(id: AvatarPlatformId): AvatarConstraints {
return avatarPlatforms[id].constraints;
}
export function getSkeletonForPlatform(id: AvatarPlatformId): SkeletonSpec {
return avatarPlatforms[id].skeleton;
}
export function canConvert(from: AvatarPlatformId, to: AvatarPlatformId): boolean {
const fromPlatform = avatarPlatforms[from];
const toPlatform = avatarPlatforms[to];
// Can always convert to universal
if (to === 'universal') return true;
// Can convert from universal to anything
if (from === 'universal') return true;
// Check if formats are compatible
const fromFormat = fromPlatform.exportFormat;
return toPlatform.importFormats.includes(fromFormat);
}