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
548 lines
20 KiB
TypeScript
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);
|
|
}
|