feat: Implement selected animation handling and integrate with human mechanics
This commit is contained in:
@@ -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">
|
||||
|
||||
@@ -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 && (
|
||||
<>
|
||||
|
||||
@@ -422,7 +422,6 @@ async function handleModelLoad(
|
||||
userId: userId,
|
||||
};
|
||||
|
||||
console.log('completeData: ', completeData);
|
||||
socket.emit("v1:model-asset:add", completeData);
|
||||
|
||||
const asset: Asset = {
|
||||
|
||||
@@ -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 />
|
||||
}
|
||||
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import React from 'react'
|
||||
|
||||
function HumanAnimator() {
|
||||
return (
|
||||
<>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default HumanAnimator
|
||||
@@ -10,7 +10,7 @@ function HumanInstance({ human }: { human: HumanStatus }) {
|
||||
return (
|
||||
<>
|
||||
|
||||
<HumanUi human={human} />
|
||||
<HumanUi />
|
||||
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
3
app/src/types/simulationTypes.d.ts
vendored
3
app/src/types/simulationTypes.d.ts
vendored
@@ -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[];
|
||||
|
||||
Reference in New Issue
Block a user