feat: auto poser

This commit is contained in:
TrèsAbhi committed 2024-01-19 22:46:10 -06:00
1 parent 47f5f86da1
commit 59abda933f
4 files changed
+123 -12

No files matched your search

@@ -1,26 +1,88 @@
import { OrbitControls } from '@react-three/drei';
import { useThree } from '@react-three/fiber';
import { useEffect, useRef } from 'react';
import usePromise from 'react-promise-suspense';
import { Vector3 } from 'three';
import { OrbitControls as OrbitControlsClass } from 'three-stdlib';
import { applyPitchYawLimits } from '../../../../../core/blitz/applyPitchYawLimits';
import { modelDefinitions } from '../../../../../core/blitzkrieg/modelDefinitions';
import { Pose, poseEvent } from '../../../../../core/blitzkrieg/pose';
import { useTankopedia } from '../../../../../stores/tankopedia';
import { Duel } from '../page';
export function Controls() {
const DISTANCE = 15;
interface ControlsProps {
duel: Duel;
}
export function Controls({ duel }: ControlsProps) {
const camera = useThree((state) => state.camera);
const orbitControls = useRef<OrbitControlsClass>(null);
const awaitedModelDefinitions = usePromise(
async () => await modelDefinitions,
[],
);
const tankModelDefinition = awaitedModelDefinitions[duel.protagonist.tank.id];
const turretModelDefinition =
tankModelDefinition.turrets[duel.protagonist.turret.id];
const gunModelDefinition =
turretModelDefinition.guns[duel.protagonist.gun.id];
const turretOrigin = new Vector3(
tankModelDefinition.turretOrigin[0],
tankModelDefinition.turretOrigin[1],
-tankModelDefinition.turretOrigin[2],
);
const gunOrigin = new Vector3(
turretModelDefinition.gunOrigin[0],
turretModelDefinition.gunOrigin[1],
-turretModelDefinition.gunOrigin[2],
);
useEffect(() => {
camera.position.set(-5, 4, -20);
orbitControls.current?.target.set(0, 1.5, 0);
const unsubscribe = useTankopedia.subscribe(
const unsubscribeTankopedia = useTankopedia.subscribe(
(state) => state.model.visual.controlsEnabled,
(enabled) => {
if (orbitControls.current) orbitControls.current.enabled = enabled;
},
);
return unsubscribe;
}, []);
function handlePoseEvent(event: Pose) {
const [pitch] = applyPitchYawLimits(
-Infinity,
0,
gunModelDefinition.pitch,
turretModelDefinition.yaw,
);
switch (event) {
case Pose.HullDown: {
camera.position
.set(0, 0, 0)
.add(turretOrigin)
.add(gunOrigin)
.add(
new Vector3(
0,
DISTANCE * Math.sin(pitch),
DISTANCE * -Math.cos(pitch),
),
);
camera.lookAt(turretOrigin.clone().add(gunOrigin));
orbitControls.current?.target.set(0, 1.5, 0);
}
}
}
poseEvent.on(handlePoseEvent);
return () => {
unsubscribeTankopedia();
poseEvent.off(handlePoseEvent);
};
}, [camera]);
return (
<OrbitControls
@@ -4,6 +4,8 @@ import { Canvas } from '@react-three/fiber';
import { Suspense, use, useEffect, useRef, useState } from 'react';
import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawLimits';
import { modelDefinitions } from '../../../../../../core/blitzkrieg/modelDefinitions';
import { modelTransformEvent } from '../../../../../../core/blitzkrieg/modelTransform';
import { Pose, poseEvent } from '../../../../../../core/blitzkrieg/pose';
import { theme } from '../../../../../../stitches.config';
import mutateTankopedia, {
TankopediaMode,
@@ -29,6 +31,11 @@ export function TankDisplay({ duel }: TankDisplayProps) {
const canvasWrapper = useRef<HTMLDivElement>(null);
const [isFullScreen, setIsFullScreen] = useState(false);
const mode = useTankopedia((state) => state.mode);
const tankModelDefinition = awaitedModelDefinitions[duel.protagonist.tank.id];
const turretModelDefinition =
tankModelDefinition.turrets[duel.protagonist.turret.id];
const gunModelDefinition =
turretModelDefinition.guns[duel.protagonist.gun.id];
function handlePointerDown() {
window.addEventListener('pointermove', handlePointerMove);
@@ -49,23 +56,37 @@ export function TankDisplay({ duel }: TankDisplayProps) {
setIsFullScreen(document.fullscreenElement !== null);
}
function handlePoseEvent(pose: Pose) {
switch (pose) {
case Pose.HullDown: {
mutateTankopedia((draft) => {
const [pitch, yaw] = applyPitchYawLimits(
-Infinity,
0,
gunModelDefinition.pitch,
turretModelDefinition.yaw,
);
modelTransformEvent.emit({ pitch, yaw });
draft.model.physical.pitch = pitch;
draft.model.physical.yaw = yaw;
});
}
}
}
poseEvent.on(handlePoseEvent);
document.addEventListener('fullscreenchange', handleFullScreenChange);
return () => {
document.removeEventListener('fullscreenchange', handleFullScreenChange);
poseEvent.off(handlePoseEvent);
};
});
useEffect(() => {
if (!duel.protagonist) return void 0;
const tankModelDefinition =
awaitedModelDefinitions[duel.protagonist.tank.id];
const turretModelDefinition =
tankModelDefinition.turrets[duel.protagonist.turret.id];
const gunModelDefinition =
turretModelDefinition.guns[duel.protagonist.gun.id];
mutateTankopedia((draft) => {
[draft.model.physical.pitch, draft.model.physical.yaw] =
applyPitchYawLimits(
@@ -116,7 +137,7 @@ export function TankDisplay({ duel }: TankDisplayProps) {
camera={{ fov: 20 }}
onPointerDown={handlePointerDown}
>
<Controls />
<Controls duel={duel} />
<SceneProps />
<Lighting />
@@ -1,10 +1,12 @@
import {
EnterFullScreenIcon,
ExitFullScreenIcon,
EyeOpenIcon,
GearIcon,
} from '@radix-ui/react-icons';
import { Button, DropdownMenu, Flex } from '@radix-ui/themes';
import { RefObject } from 'react';
import { Pose, poseEvent } from '../../../../../../../core/blitzkrieg/pose';
import mutateTankopedia, {
useTankopedia,
} from '../../../../../../../stores/tankopedia';
@@ -30,6 +32,25 @@ export function Options({ isFullScreen, canvas }: OptionsProps) {
right: 18,
}}
>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
<Button color={showGrid ? 'purple' : 'gray'} variant="ghost">
<EyeOpenIcon />
</Button>
</DropdownMenu.Trigger>
<DropdownMenu.Content>
<DropdownMenu.Item
onClick={() => {
console.log('called?');
poseEvent.emit(Pose.HullDown);
}}
>
Hull down
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
<Button color={showGrid ? 'purple' : 'gray'} variant="ghost">
+7
View File
@@ -0,0 +1,7 @@
import { EventManager } from './eventManager';
export enum Pose {
HullDown,
}
export const poseEvent = new EventManager<Pose>();