| import React, {createContext, useContext, useEffect, useRef, useState} from 'react'; | |
| import { | |
| Accordion, | |
| AccordionSummary, | |
| AccordionDetails, FormControl, FormLabel, RadioGroup, FormControlLabel, Radio, | |
| } from '@mui/material'; | |
| import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; | |
| import Box from '@mui/material/Box'; | |
| import {Button} from "@mui/material"; | |
| import {TreeItem, TreeView} from "@mui/lab"; | |
| import ChevronRightIcon from "@mui/icons-material/ChevronRight"; | |
| const ObjectContext = createContext([]); | |
| function useObjectUpdate() { | |
| const context = useContext(ObjectContext); | |
| if (!context) { | |
| throw new Error('useObjectUpdate must be used within ObjectProvider'); | |
| } | |
| return context; | |
| } | |
| function ObjectProvider({children}) { | |
| const [objects, setObjects] = useState([]); | |
| const updateObjects = (newObjects) => { | |
| setObjects(newObjects); | |
| }; | |
| return (<ObjectContext.Provider value={{objects, updateObjects}}> | |
| {children} | |
| </ObjectContext.Provider>); | |
| } | |
| function processNode(node, handlePoseSelectedObject, setSelectedObj, transformControlMap) { | |
| if (!node) { | |
| return null; | |
| } | |
| return (<TreeItem key={node.name} nodeId={node.name} label={node.name} onClick={(objEvent) => { | |
| const objName = objEvent.target.innerHTML; | |
| setSelectedObj(objName); | |
| handlePoseSelectedObject(objName, transformControlMap[objName]); | |
| }}> | |
| {node.children && node.children.map((child) => processNode(child, handlePoseSelectedObject, setSelectedObj, transformControlMap))} | |
| </TreeItem>); | |
| } | |
| const boneNameTransformMap = { | |
| 'hips': "none", | |
| 'spine': "none", | |
| 'chest': "none", | |
| 'upperChest': "none", | |
| 'neck': "none", | |
| 'head': "none", | |
| 'leftShoulder': "none", | |
| 'leftUpperArm': "none", | |
| 'leftLowerArm': "none", | |
| 'leftHand': "none", | |
| 'leftThumbMetacarpal': "none", | |
| 'leftThumbProximal': "none", | |
| 'leftThumbDistal': "none", | |
| 'leftIndexProximal': "none", | |
| 'leftIndexIntermediate': "none", | |
| 'leftIndexDistal': "none", | |
| 'leftMiddleProximal': "none", | |
| 'leftMiddleIntermediate': "none", | |
| 'leftMiddleDistal': "none", | |
| 'leftRingProximal': "none", | |
| 'leftRingIntermediate': "none", | |
| 'leftRingDistal': "none", | |
| 'leftLittleProximal': "none", | |
| 'leftLittleIntermediate': "none", | |
| 'leftLittleDistal': "none", | |
| 'rightShoulder': "none", | |
| 'rightUpperArm': "none", | |
| 'rightLowerArm': "none", | |
| 'rightHand': "none", | |
| 'rightLittleProximal': "none", | |
| 'rightLittleIntermediate': "none", | |
| 'rightLittleDistal': "none", | |
| 'rightRingProximal': "none", | |
| 'rightRingIntermediate': "none", | |
| 'rightRingDistal': "none", | |
| 'rightMiddleProximal': "none", | |
| 'rightMiddleIntermediate': "none", | |
| 'rightMiddleDistal': "none", | |
| 'rightIndexProximal': "none", | |
| 'rightIndexIntermediate': "none", | |
| 'rightIndexDistal': "none", | |
| 'rightThumbMetacarpal': "none", | |
| 'rightThumbProximal': "none", | |
| 'rightThumbDistal': "none", | |
| 'leftUpperLeg': "none", | |
| 'leftLowerLeg': "none", | |
| 'leftFoot': "none", | |
| 'leftToes': "none", | |
| 'rightUpperLeg': "none", | |
| 'rightLowerLeg': "none", | |
| 'rightFoot': "none", | |
| 'rightToes': "none" | |
| }; | |
| const boneNameList = { | |
| "name": "body", | |
| "children": [ | |
| { | |
| "name": "mainBody", | |
| "children": [ | |
| { | |
| "name": "hips", | |
| }, | |
| { | |
| "name": "spine", | |
| }, | |
| { | |
| "name": "chest", | |
| }, | |
| { | |
| "name": "upperChest", | |
| }, | |
| { | |
| "name": "head", | |
| } | |
| ] | |
| }, | |
| { | |
| "name": "leftArm", | |
| "children": [ | |
| { | |
| "name": "leftShoulder", | |
| }, | |
| { | |
| "name": "leftUpperArm", | |
| }, | |
| { | |
| "name": "leftLowerArm", | |
| }, | |
| { | |
| "name": "leftHand", | |
| "children": [ | |
| { | |
| "name": "leftThumb", | |
| "children": [ | |
| { | |
| "name": "leftThumbMetacarpal", | |
| }, | |
| { | |
| "name": "leftThumbProximal", | |
| }, | |
| { | |
| "name": "leftThumbDistal", | |
| }, | |
| ] | |
| }, | |
| { | |
| "name": "leftIndex", | |
| "children": [ | |
| { | |
| "name": "leftIndexProximal", | |
| }, | |
| { | |
| "name": "leftIndexIntermediate", | |
| }, | |
| { | |
| "name": "leftIndexDistal", | |
| }, | |
| ] | |
| }, | |
| { | |
| "name": "leftMiddle", | |
| "children": [ | |
| { | |
| "name": "leftMiddleProximal", | |
| }, | |
| { | |
| "name": "leftMiddleIntermediate", | |
| }, | |
| { | |
| "name": "leftMiddleDistal", | |
| }, | |
| ] | |
| }, | |
| { | |
| "name": "leftRing", | |
| "children": [ | |
| { | |
| "name": "leftRingProximal", | |
| }, | |
| { | |
| "name": "leftRingIntermediate", | |
| }, | |
| { | |
| "name": "leftRingDistal", | |
| }, | |
| ] | |
| }, | |
| { | |
| "name": "leftLittle", | |
| "children": [ | |
| { | |
| "name": "leftLittleProximal", | |
| }, | |
| { | |
| "name": "leftLittleIntermediate", | |
| }, | |
| { | |
| "name": "leftLittleDistal", | |
| } | |
| ] | |
| }, | |
| ] | |
| }, | |
| ] | |
| }, | |
| { | |
| "name": "rightArm", | |
| "children": [ | |
| { | |
| "name": "rightShoulder", | |
| }, | |
| { | |
| "name": "rightUpperArm", | |
| }, | |
| { | |
| "name": "rightLowerArm", | |
| }, | |
| { | |
| "name": "rightHand", | |
| "children": [ | |
| { | |
| "name": "rightThumb", | |
| "children": [ | |
| { | |
| "name": "rightThumbMetacarpal", | |
| }, | |
| { | |
| "name": "rightThumbProximal", | |
| }, | |
| { | |
| "name": "rightThumbDistal", | |
| }, | |
| ] | |
| }, | |
| { | |
| "name": "rightIndex", | |
| "children": [ | |
| { | |
| "name": "rightIndexProximal", | |
| }, | |
| { | |
| "name": "rightIndexIntermediate", | |
| }, | |
| { | |
| "name": "rightIndexDistal", | |
| }, | |
| ] | |
| }, | |
| { | |
| "name": "rightMiddle", | |
| "children": [ | |
| { | |
| "name": "rightMiddleProximal", | |
| }, | |
| { | |
| "name": "rightMiddleIntermediate", | |
| }, | |
| { | |
| "name": "rightMiddleDistal", | |
| }, | |
| ] | |
| }, | |
| { | |
| "name": "rightRing", | |
| "children": [ | |
| { | |
| "name": "rightRingProximal", | |
| }, | |
| { | |
| "name": "rightRingIntermediate", | |
| }, | |
| { | |
| "name": "rightRingDistal", | |
| }, | |
| ] | |
| }, | |
| { | |
| "name": "rightLittle", | |
| "children": [ | |
| { | |
| "name": "rightLittleProximal", | |
| }, | |
| { | |
| "name": "rightLittleIntermediate", | |
| }, | |
| { | |
| "name": "rightLittleDistal", | |
| }, | |
| ] | |
| } | |
| ] | |
| }, | |
| ] | |
| }, | |
| { | |
| "name": "leftLeg", | |
| "children": [ | |
| { | |
| "name": "leftUpperLeg", | |
| }, | |
| { | |
| "name": "leftLowerLeg", | |
| }, | |
| { | |
| "name": "leftFoot", | |
| }, | |
| { | |
| "name": "leftToes", | |
| } | |
| ] | |
| }, | |
| { | |
| "name": "rightLeg", | |
| "children": [ | |
| { | |
| "name": "rightUpperLeg", | |
| }, | |
| { | |
| "name": "rightLowerLeg", | |
| }, | |
| { | |
| "name": "rightFoot", | |
| }, | |
| { | |
| "name": "rightToes", | |
| } | |
| ] | |
| } | |
| ] | |
| } | |
| function BodyTree({handlePoseSelectedObject, setSelectedObj, transformControlMap}) { | |
| return (<TreeView | |
| defaultCollapseIcon={<ExpandMoreIcon/>} | |
| defaultExpandIcon={<ChevronRightIcon/>} | |
| defaultEndIcon={<div/>} | |
| > | |
| {processNode(boneNameList, handlePoseSelectedObject, setSelectedObj, transformControlMap)} | |
| </TreeView>); | |
| } | |
| export default function PosePanel({ | |
| setPoseModelFileName, | |
| handlePoseSelectedObject, | |
| }) { | |
| return (<ObjectProvider> | |
| <BodyTreeWrapper setPoseModelFileName={setPoseModelFileName} | |
| handlePoseSelectedObject={handlePoseSelectedObject}/> | |
| </ObjectProvider>) | |
| } | |
| function BodyTreeWrapper({ | |
| setPoseModelFileName, | |
| handlePoseSelectedObject | |
| }) { | |
| const [selectedObj, setSelectedObj] = useState(null); | |
| const loadPoseModel = () => { | |
| setPoseModelFileName("pose.vrm"); | |
| }; | |
| const {updateObjects} = useObjectUpdate(); | |
| const [transformControlMap, setTransformControlMap] = useState(boneNameTransformMap); | |
| const updateObjectsRef = useRef(); | |
| updateObjectsRef.current = updateObjects; | |
| useEffect(() => { | |
| window.updateObjects = (newObjects) => { | |
| if (updateObjectsRef.current) { | |
| updateObjectsRef.current(newObjects); | |
| } | |
| }; | |
| return () => { | |
| window.updateObjects = null; | |
| }; | |
| }, []); | |
| return (<div> | |
| <Box mb={1} mt={1}> | |
| <Accordion> | |
| <AccordionSummary expandIcon={<ExpandMoreIcon/>}> | |
| Pose | |
| </AccordionSummary> | |
| <AccordionDetails> | |
| <Button variant="contained" color="primary" fullWidth sx={{margin: '2px'}} | |
| onClick={loadPoseModel}>Load Pose Model (VRM)</Button> | |
| <div><h6>Pose edit only supports VRM file currently.</h6></div> | |
| <BodyTree handlePoseSelectedObject={handlePoseSelectedObject} setSelectedObj={setSelectedObj} transformControlMap={transformControlMap}/> | |
| { (selectedObj in boneNameTransformMap) && <FormControl> | |
| <FormLabel>Operate</FormLabel> | |
| <RadioGroup | |
| aria-labelledby="operate-radio-buttons-group-label" | |
| defaultValue="none" | |
| name="operate-radio-buttons-group" | |
| row={true} | |
| onChange={(event) => { | |
| handlePoseSelectedObject(selectedObj, event.target.value); | |
| const updatedMap = {...transformControlMap}; | |
| updatedMap[selectedObj] = event.target.value; | |
| setTransformControlMap(updatedMap); | |
| }} | |
| > | |
| <FormControlLabel value="none" control={<Radio/>} label="None" | |
| checked={transformControlMap[selectedObj] === "none"}/> | |
| <FormControlLabel value="rotate" control={<Radio/>} label="Rotate" | |
| checked={transformControlMap[selectedObj] === "rotate"}/> | |
| </RadioGroup> | |
| </FormControl>} | |
| </AccordionDetails> | |
| </Accordion> | |
| </Box> | |
| </div>); | |
| } | |