From 8dd853dd03d047ceabd11393e38ad1c2cff1b01e Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Thu, 3 Jul 2025 15:18:49 +0530 Subject: [PATCH] feat: Implement selected animation handling and integrate with human mechanics --- .../components/AnimationList.tsx | 11 +- .../mechanics/humanMechanics.tsx | 27 +- .../builder/asset/functions/addAssetModel.ts | 1 - app/src/modules/simulation/human/human.tsx | 27 +- .../instance/animator/humanAnimator.tsx | 10 + .../instances/instance/humanInstance.tsx | 2 +- .../human/instances/instance/humanUi.tsx | 277 +++++++++++++++++- .../spatialUI/vehicle/vehicleUI.tsx | 4 +- .../store/simulation/useSimulationStore.ts | 34 +++ app/src/types/simulationTypes.d.ts | 3 +- 10 files changed, 351 insertions(+), 45 deletions(-) create mode 100644 app/src/modules/simulation/human/instances/instance/animator/humanAnimator.tsx diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/components/AnimationList.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/components/AnimationList.tsx index f3283e3..836432d 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/components/AnimationList.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/components/AnimationList.tsx @@ -1,7 +1,7 @@ import React, { useRef } from "react"; import { AddIcon, RemoveIcon, ResizeHeightIcon } from "../../../../../icons/ExportCommonIcons"; import { handleResize } from "../../../../../../functions/handleResizePannel"; -import { useSelectedAction } from "../../../../../../store/simulation/useSimulationStore"; +import { useSelectedAction, useSelectedAnimation } from "../../../../../../store/simulation/useSimulationStore"; import RenameInput from "../../../../../ui/inputs/RenameInput"; interface AnimationListProps { @@ -17,13 +17,6 @@ interface AnimationListProps { onRemoveAnimation: (animationUuid: string) => void; handleAnimationSelect: (animationUuid: string) => void; handleRenameAnimation: (animationUuid: string, newName: string) => void; - selectedAnimation: { - animationUuid: string; - animationName: string; - animationType: "behaviour" | "animatedTravel"; - animation: string | null; - travelPoints?: { startPoint: [number, number, number] | null; endPoint: [number, number, number] | null }; - } | undefined } const AnimationList: React.FC = ({ @@ -32,10 +25,10 @@ const AnimationList: React.FC = ({ onRemoveAnimation, handleAnimationSelect, handleRenameAnimation, - selectedAnimation }) => { const animationContainerRef = useRef(null); const { selectedAction } = useSelectedAction(); + const { selectedAnimation } = useSelectedAnimation(); return (
diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx index 24bb2e1..17e8363 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx @@ -7,7 +7,7 @@ import Trigger from "../trigger/Trigger"; import PickAndPlaceAction from "../actions/PickAndPlaceAction"; import ActionsList from "../components/ActionsList"; import AnimationList from "../components/AnimationList"; -import { useSelectedEventData, useSelectedAction } from "../../../../../../store/simulation/useSimulationStore"; +import { useSelectedEventData, useSelectedAction, useSelectedAnimation } from "../../../../../../store/simulation/useSimulationStore"; import { upsertProductOrEventApi } from "../../../../../../services/simulation/products/UpsertProductOrEventApi"; import { useProductContext } from "../../../../../../modules/simulation/products/productContext"; import { useVersionContext } from "../../../../../../modules/builder/version/versionContext"; @@ -26,14 +26,8 @@ function HumanMechanics() { const { getPointByUuid, getEventByModelUuid, updateEvent, updateAction, addAction, removeAction } = productStore(); const { selectedProductStore } = useProductContext(); const { selectedProduct } = selectedProductStore(); - const [selectedAnimation, setSelectedAnimation] = useState<{ - animationUuid: string; - animationName: string; - animationType: "behaviour" | "animatedTravel"; - animation: string | null; - travelPoints?: { startPoint: [number, number, number] | null; endPoint: [number, number, number] | null; } - }>(); const { selectedAction, setSelectedAction, clearSelectedAction } = useSelectedAction(); + const { selectedAnimation, setSelectedAnimation } = useSelectedAnimation(); const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); const { projectId } = useParams(); @@ -210,10 +204,8 @@ function HumanMechanics() { if (!updatedAnimation) return; - updatedAnimation.travelPoints = { - startPoint: null, - endPoint: null - }; + delete updatedAnimation.startPoint; + delete updatedAnimation.endPoint; const updatedAction = { ...currentAction, @@ -387,14 +379,8 @@ function HumanMechanics() { animationType: newType }; - if (newType === 'animatedTravel') { - updatedAnim.travelPoints = { - startPoint: null, - endPoint: null - }; - } else { - delete updatedAnim.travelPoints; - } + delete updatedAnim.startPoint; + delete updatedAnim.endPoint; return updatedAnim; } @@ -546,7 +532,6 @@ function HumanMechanics() { onRemoveAnimation={handleRemoveAnimation} handleAnimationSelect={handleAnimationSelect} handleRenameAnimation={handleRenameAnimation} - selectedAnimation={selectedAnimation} /> {selectedAnimation && ( <> diff --git a/app/src/modules/builder/asset/functions/addAssetModel.ts b/app/src/modules/builder/asset/functions/addAssetModel.ts index 8aa18f1..f7614ee 100644 --- a/app/src/modules/builder/asset/functions/addAssetModel.ts +++ b/app/src/modules/builder/asset/functions/addAssetModel.ts @@ -422,7 +422,6 @@ async function handleModelLoad( userId: userId, }; - console.log('completeData: ', completeData); socket.emit("v1:model-asset:add", completeData); const asset: Asset = { diff --git a/app/src/modules/simulation/human/human.tsx b/app/src/modules/simulation/human/human.tsx index 6a552c6..49c9435 100644 --- a/app/src/modules/simulation/human/human.tsx +++ b/app/src/modules/simulation/human/human.tsx @@ -1,32 +1,43 @@ +import { useEffect, useState } from 'react' +import { useSelectedAnimation, useSelectedEventSphere } from '../../../store/simulation/useSimulationStore'; +import { usePlayButtonStore } from '../../../store/usePlayButtonStore'; +import { useSceneContext } from '../../scene/sceneContext'; import HumanInstances from './instances/humanInstances' -import { useEffect, useState } from "react"; -import { useSelectedEventSphere } from "../../../store/simulation/useSimulationStore"; -import { usePlayButtonStore } from "../../../store/usePlayButtonStore"; -import { useSceneContext } from "../../scene/sceneContext"; +import HumanUi from './instances/instance/humanUi'; function Human() { const { humanStore } = useSceneContext(); const { getHumanById } = humanStore(); + const { selectedAnimation } = useSelectedAnimation(); const { selectedEventSphere } = useSelectedEventSphere(); const { isPlaying } = usePlayButtonStore(); - const [isHumanSelected, setIsHumanSelected] = useState(false); + const [isVehicleSelected, setIsHumanSelected] = useState(false); useEffect(() => { if (selectedEventSphere) { - const selectedVehicle = getHumanById(selectedEventSphere.userData.modelUuid); - if (selectedVehicle) { + const selectedHuman = getHumanById(selectedEventSphere.userData.modelUuid); + if (selectedHuman && + selectedHuman.point.actions.some((action) => + action.animationSequences.some((animation) => + animation.animationUuid === selectedAnimation?.animationUuid && animation.animationType === 'animatedTravel' + ) + )) { setIsHumanSelected(true); } else { setIsHumanSelected(false); } } - }, [getHumanById, selectedEventSphere]) + }, [getHumanById, selectedEventSphere, selectedAnimation]) return ( <> + {isVehicleSelected && selectedEventSphere && !isPlaying && + + } + ) } diff --git a/app/src/modules/simulation/human/instances/instance/animator/humanAnimator.tsx b/app/src/modules/simulation/human/instances/instance/animator/humanAnimator.tsx new file mode 100644 index 0000000..01f569e --- /dev/null +++ b/app/src/modules/simulation/human/instances/instance/animator/humanAnimator.tsx @@ -0,0 +1,10 @@ +import React from 'react' + +function HumanAnimator() { + return ( + <> + + ) +} + +export default HumanAnimator \ No newline at end of file diff --git a/app/src/modules/simulation/human/instances/instance/humanInstance.tsx b/app/src/modules/simulation/human/instances/instance/humanInstance.tsx index 89e2b10..25fb044 100644 --- a/app/src/modules/simulation/human/instances/instance/humanInstance.tsx +++ b/app/src/modules/simulation/human/instances/instance/humanInstance.tsx @@ -10,7 +10,7 @@ function HumanInstance({ human }: { human: HumanStatus }) { return ( <> - + ) diff --git a/app/src/modules/simulation/human/instances/instance/humanUi.tsx b/app/src/modules/simulation/human/instances/instance/humanUi.tsx index 7a2bb9c..cfb0fc9 100644 --- a/app/src/modules/simulation/human/instances/instance/humanUi.tsx +++ b/app/src/modules/simulation/human/instances/instance/humanUi.tsx @@ -1,8 +1,281 @@ -import React from 'react' +import { useEffect, useRef, useState } from 'react' +import { useGLTF } from '@react-three/drei'; +import { useFrame, useThree } from '@react-three/fiber'; +import { useIsDragging, useIsRotating, useSelectedAction, useSelectedEventSphere } from '../../../../../store/simulation/useSimulationStore'; +import { useProductContext } from '../../../products/productContext'; +import { useSceneContext } from '../../../../scene/sceneContext'; +import { Group, Plane, Vector3 } from 'three'; +import { useVersionContext } from '../../../../builder/version/versionContext'; +import { useParams } from 'react-router-dom'; +import startPoint from "../../../../../assets/gltf-glb/ui/arrow_green.glb"; +import startEnd from "../../../../../assets/gltf-glb/ui/arrow_red.glb"; +import { upsertProductOrEventApi } from '../../../../../services/simulation/products/UpsertProductOrEventApi'; + +function HumanUi() { + const { scene: startScene } = useGLTF(startPoint) as any; + const { scene: endScene } = useGLTF(startEnd) as any; + const startMarker = useRef(null); + const endMarker = useRef(null); + const outerGroup = useRef(null); + const prevMousePos = useRef({ x: 0, y: 0 }); + const { controls, raycaster } = useThree(); + const { selectedEventSphere } = useSelectedEventSphere(); + const { selectedProductStore } = useProductContext(); + const { humanStore, productStore } = useSceneContext(); + const { selectedProduct } = selectedProductStore(); + const { humans, getHumanById } = humanStore(); + const { updateEvent } = productStore(); + const [startPosition, setStartPosition] = useState<[number, number, number]>([0, 1, 0]); + const [endPosition, setEndPosition] = useState<[number, number, number]>([0, 1, 0]); + const [startRotation, setStartRotation] = useState<[number, number, number]>([0, 0, 0]); + const [endRotation, setEndRotation] = useState<[number, number, number]>([0, 0, 0]); + const { isDragging, setIsDragging } = useIsDragging(); + const { isRotating, setIsRotating } = useIsRotating(); + const plane = useRef(new Plane(new Vector3(0, 1, 0), 0)); + + const [selectedHumanData, setSelectedHumanData] = useState<{ + position: [number, number, number]; + rotation: [number, number, number]; + }>({ position: [0, 0, 0], rotation: [0, 0, 0] }); + + const { selectedAction } = useSelectedAction(); + const { selectedVersionStore } = useVersionContext(); + const { selectedVersion } = selectedVersionStore(); + const { projectId } = useParams(); + + const updateBackend = ( + productName: string, + productUuid: string, + projectId: string, + eventData: EventsSchema + ) => { + upsertProductOrEventApi({ + productName: productName, + productUuid: productUuid, + projectId: projectId, + eventDatas: eventData, + versionId: selectedVersion?.versionId || '', + }); + }; + + useEffect(() => { + if (!selectedEventSphere) return; + const selectedHuman = getHumanById(selectedEventSphere.userData.modelUuid); + + if (selectedHuman) { + setSelectedHumanData({ + position: selectedHuman.position, + rotation: selectedHuman.rotation, + }); + + if (selectedHuman.point?.actions && selectedAction.actionId) { + const action = selectedHuman.point.actions.find(a => a.actionUuid === selectedAction.actionId); + + if (action?.animationSequences[0]) { + const sequence = action.animationSequences[0]; + + if (sequence.startPoint?.position && outerGroup.current) { + const worldPos = new Vector3(...sequence.startPoint.position); + const localPosition = outerGroup.current.worldToLocal(worldPos.clone()); + setStartPosition([localPosition.x, 0.5, localPosition.z]); + setStartRotation(sequence.startPoint.rotation || [0, 0, 0]); + } + + if (sequence.endPoint?.position && outerGroup.current) { + const worldPos = new Vector3(...sequence.endPoint.position); + const localPosition = outerGroup.current.worldToLocal(worldPos.clone()); + setEndPosition([localPosition.x, 0.5, localPosition.z]); + setEndRotation(sequence.endPoint.rotation || [0, 0, 0]); + } + } + } + } + }, [selectedEventSphere, outerGroup.current, selectedAction, humans]); + + const handlePointerDown = ( + e: any, + state: "start" | "end", + rotation: "start" | "end" + ) => { + if (e.object.name === "handle") { + const normalizedX = (e.clientX / window.innerWidth) * 2 - 1; + const normalizedY = -(e.clientY / window.innerHeight) * 2 + 1; + prevMousePos.current = { x: normalizedX, y: normalizedY }; + setIsRotating(rotation); + if (controls) (controls as any).enabled = false; + setIsDragging(null); + } else { + setIsDragging(state); + setIsRotating(null); + if (controls) (controls as any).enabled = false; + } + }; + + const handlePointerUp = () => { + (controls as any).enabled = true; + setIsDragging(null); + setIsRotating(null); + + if (selectedEventSphere?.userData.modelUuid && selectedAction.actionId) { + const selectedHuman = getHumanById(selectedEventSphere.userData.modelUuid); + + if (selectedHuman && outerGroup.current && startMarker.current && endMarker.current) { + const worldPosStart = new Vector3(...startPosition); + const globalStartPosition = outerGroup.current.localToWorld(worldPosStart.clone()); + + const worldPosEnd = new Vector3(...endPosition); + const globalEndPosition = outerGroup.current.localToWorld(worldPosEnd.clone()); + + const updatedActions = selectedHuman.point.actions.map(action => { + if (action.actionUuid === selectedAction.actionId) { + const updatedSequences = action.animationSequences.map(sequence => { + return { + ...sequence, + startPoint: { + position: [globalStartPosition.x, globalStartPosition.y, globalStartPosition.z] as [number, number, number], + rotation: startRotation + }, + endPoint: { + position: [globalEndPosition.x, globalEndPosition.y, globalEndPosition.z] as [number, number, number], + rotation: endRotation + } + }; + }); + + return { + ...action, + animationSequences: updatedSequences + }; + } + return action; + }); + + const event = updateEvent( + selectedProduct.productUuid, + selectedEventSphere.userData.modelUuid, + { + ...selectedHuman, + point: { + ...selectedHuman.point, + actions: updatedActions + } + } + ); + + if (event) { + updateBackend( + selectedProduct.productName, + selectedProduct.productUuid, + projectId || '', + event + ); + } + } + } + }; + + useFrame(() => { + if (!isDragging || !plane.current || !raycaster || !outerGroup.current) return; + const intersectPoint = new Vector3(); + const intersects = raycaster.ray.intersectPlane( + plane.current, + intersectPoint + ); + if (!intersects) return; + const localPoint = outerGroup?.current.worldToLocal(intersectPoint.clone()); + if (isDragging === "start") { + setStartPosition([localPoint.x, 0.5, localPoint.z]); + } else if (isDragging === "end") { + setEndPosition([localPoint.x, 0.5, localPoint.z]); + } + }); + + useFrame((state) => { + if (!isRotating) return; + const currentPointerX = state.pointer.x; + const deltaX = currentPointerX - prevMousePos.current.x; + prevMousePos.current.x = currentPointerX; + const marker = + isRotating === "start" ? startMarker.current : endMarker.current; + if (marker) { + const rotationSpeed = 10; + marker.rotation.y += deltaX * rotationSpeed; + if (isRotating === "start") { + setStartRotation([ + marker.rotation.x, + marker.rotation.y, + marker.rotation.z, + ]); + } else { + setEndRotation([ + marker.rotation.x, + marker.rotation.y, + marker.rotation.z, + ]); + } + } + }); + + useEffect(() => { + const handleGlobalPointerUp = () => { + setIsDragging(null); + setIsRotating(null); + if (controls) (controls as any).enabled = true; + handlePointerUp(); + }; + + if (isDragging || isRotating) { + window.addEventListener("pointerup", handleGlobalPointerUp); + } + + return () => { + window.removeEventListener("pointerup", handleGlobalPointerUp); + }; + }, [isDragging, isRotating, startPosition, startRotation, endPosition, endRotation]); -function HumanUi({ human }: { human: HumanStatus }) { return ( <> + {selectedHumanData && ( + + { + e.stopPropagation(); + handlePointerDown(e, "start", "start"); + }} + onPointerMissed={() => { + (controls as any).enabled = true; + setIsDragging(null); + setIsRotating(null); + }} + /> + + { + e.stopPropagation(); + handlePointerDown(e, "end", "end"); + }} + onPointerMissed={() => { + (controls as any).enabled = true; + setIsDragging(null); + setIsRotating(null); + }} + /> + + )} ) } diff --git a/app/src/modules/simulation/spatialUI/vehicle/vehicleUI.tsx b/app/src/modules/simulation/spatialUI/vehicle/vehicleUI.tsx index 9d889ed..02eb2ec 100644 --- a/app/src/modules/simulation/spatialUI/vehicle/vehicleUI.tsx +++ b/app/src/modules/simulation/spatialUI/vehicle/vehicleUI.tsx @@ -43,7 +43,7 @@ const VehicleUI = () => { const outerGroup = useRef(null); const state: Types.ThreeState = useThree(); const controls: any = state.controls; - const [selectedVehicleData, setSelectedVechicleData] = useState<{ position: [number, number, number]; rotation: [number, number, number]; }>({ position: [0, 0, 0], rotation: [0, 0, 0] }); + const [selectedVehicleData, setSelectedVehicleData] = useState<{ position: [number, number, number]; rotation: [number, number, number]; }>({ position: [0, 0, 0], rotation: [0, 0, 0] }); const CIRCLE_RADIUS = 0.8; const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); @@ -71,7 +71,7 @@ const VehicleUI = () => { ); if (selectedVehicle) { - setSelectedVechicleData({ + setSelectedVehicleData({ position: selectedVehicle.position, rotation: selectedVehicle.rotation, }); diff --git a/app/src/store/simulation/useSimulationStore.ts b/app/src/store/simulation/useSimulationStore.ts index 1bbab4c..b6cd076 100644 --- a/app/src/store/simulation/useSimulationStore.ts +++ b/app/src/store/simulation/useSimulationStore.ts @@ -146,6 +146,40 @@ export const useSelectedAction = create()( })) ); +interface SelectedAnimationState { + selectedAnimation: { + animationUuid: string; + animationName: string; + animationType: "behaviour" | "animatedTravel"; + animation: string | null; + travelPoints?: { startPoint: [number, number, number] | null; endPoint: [number, number, number] | null; } + } | null; + setSelectedAnimation: (animation: { + animationUuid: string; + animationName: string; + animationType: "behaviour" | "animatedTravel"; + animation: string | null; + travelPoints?: { startPoint: [number, number, number] | null; endPoint: [number, number, number] | null; } + }) => void; + clearSelectedAnimation: () => void; +} + +export const useSelectedAnimation = create()( + immer((set) => ({ + selectedAnimation: null, + setSelectedAnimation: (animation) => { + set((state) => { + state.selectedAnimation = animation; + }); + }, + clearSelectedAnimation: () => { + set((state) => { + state.selectedAnimation = null; + }); + }, + })) +); + interface IsDraggingState { isDragging: "start" | "end" | null; setIsDragging: (state: "start" | "end" | null) => void; diff --git a/app/src/types/simulationTypes.d.ts b/app/src/types/simulationTypes.d.ts index b99feee..02d66c8 100644 --- a/app/src/types/simulationTypes.d.ts +++ b/app/src/types/simulationTypes.d.ts @@ -78,7 +78,8 @@ interface HumanAction { animationName: string; animationType: "behaviour" | "animatedTravel"; animation: string | null; - travelPoints?: { startPoint: [number, number, number] | null; endPoint: [number, number, number] | null; } + startPoint?: { position: [number, number, number] | null; rotation: [number, number, number] | null; } + endPoint?: { position: [number, number, number] | null; rotation: [number, number, number] | null; } }[] loadCapacity: number; triggers: TriggerSchema[];