feat: Implement selected animation handling and integrate with human mechanics

This commit is contained in:
2025-07-03 15:18:49 +05:30
parent eb5683eadc
commit 8dd853dd03
10 changed files with 351 additions and 45 deletions

View File

@@ -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<AnimationListProps> = ({
@@ -32,10 +25,10 @@ const AnimationList: React.FC<AnimationListProps> = ({
onRemoveAnimation,
handleAnimationSelect,
handleRenameAnimation,
selectedAnimation
}) => {
const animationContainerRef = useRef<HTMLDivElement>(null);
const { selectedAction } = useSelectedAction();
const { selectedAnimation } = useSelectedAnimation();
return (
<div className="actions-list-container">

View File

@@ -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 && (
<>

View File

@@ -422,7 +422,6 @@ async function handleModelLoad(
userId: userId,
};
console.log('completeData: ', completeData);
socket.emit("v1:model-asset:add", completeData);
const asset: Asset = {

View File

@@ -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 (
<>
<HumanInstances />
{isVehicleSelected && selectedEventSphere && !isPlaying &&
<HumanUi />
}
</>
)
}

View File

@@ -0,0 +1,10 @@
import React from 'react'
function HumanAnimator() {
return (
<>
</>
)
}
export default HumanAnimator

View File

@@ -10,7 +10,7 @@ function HumanInstance({ human }: { human: HumanStatus }) {
return (
<>
<HumanUi human={human} />
<HumanUi />
</>
)

View File

@@ -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<Group>(null);
const endMarker = useRef<Group>(null);
const outerGroup = useRef<Group>(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 && (
<group
position={selectedHumanData.position}
rotation={selectedHumanData.rotation}
ref={outerGroup}
>
<primitive
name="startMarker"
object={startScene}
ref={startMarker}
position={startPosition}
rotation={startRotation}
onPointerDown={(e: any) => {
e.stopPropagation();
handlePointerDown(e, "start", "start");
}}
onPointerMissed={() => {
(controls as any).enabled = true;
setIsDragging(null);
setIsRotating(null);
}}
/>
<primitive
name="endMarker"
object={endScene}
ref={endMarker}
position={endPosition}
rotation={endRotation}
onPointerDown={(e: any) => {
e.stopPropagation();
handlePointerDown(e, "end", "end");
}}
onPointerMissed={() => {
(controls as any).enabled = true;
setIsDragging(null);
setIsRotating(null);
}}
/>
</group>
)}
</>
)
}

View File

@@ -43,7 +43,7 @@ const VehicleUI = () => {
const outerGroup = useRef<Group>(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,
});

View File

@@ -146,6 +146,40 @@ export const useSelectedAction = create<SelectedActionState>()(
}))
);
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<SelectedAnimationState>()(
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;

View File

@@ -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[];