mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-24 20:19:49 +02:00
feat: auto poser
This commit is contained in:
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 { OrbitControls } from '@react-three/drei';
|
||||||
import { useThree } from '@react-three/fiber';
|
import { useThree } from '@react-three/fiber';
|
||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
|
import usePromise from 'react-promise-suspense';
|
||||||
|
import { Vector3 } from 'three';
|
||||||
import { OrbitControls as OrbitControlsClass } from 'three-stdlib';
|
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 { 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 camera = useThree((state) => state.camera);
|
||||||
const orbitControls = useRef<OrbitControlsClass>(null);
|
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(() => {
|
useEffect(() => {
|
||||||
camera.position.set(-5, 4, -20);
|
camera.position.set(-5, 4, -20);
|
||||||
orbitControls.current?.target.set(0, 1.5, 0);
|
orbitControls.current?.target.set(0, 1.5, 0);
|
||||||
|
|
||||||
const unsubscribe = useTankopedia.subscribe(
|
const unsubscribeTankopedia = useTankopedia.subscribe(
|
||||||
(state) => state.model.visual.controlsEnabled,
|
(state) => state.model.visual.controlsEnabled,
|
||||||
(enabled) => {
|
(enabled) => {
|
||||||
if (orbitControls.current) orbitControls.current.enabled = 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 (
|
return (
|
||||||
<OrbitControls
|
<OrbitControls
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import { Canvas } from '@react-three/fiber';
|
|||||||
import { Suspense, use, useEffect, useRef, useState } from 'react';
|
import { Suspense, use, useEffect, useRef, useState } from 'react';
|
||||||
import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawLimits';
|
import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawLimits';
|
||||||
import { modelDefinitions } from '../../../../../../core/blitzkrieg/modelDefinitions';
|
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 { theme } from '../../../../../../stitches.config';
|
||||||
import mutateTankopedia, {
|
import mutateTankopedia, {
|
||||||
TankopediaMode,
|
TankopediaMode,
|
||||||
@@ -29,6 +31,11 @@ export function TankDisplay({ duel }: TankDisplayProps) {
|
|||||||
const canvasWrapper = useRef<HTMLDivElement>(null);
|
const canvasWrapper = useRef<HTMLDivElement>(null);
|
||||||
const [isFullScreen, setIsFullScreen] = useState(false);
|
const [isFullScreen, setIsFullScreen] = useState(false);
|
||||||
const mode = useTankopedia((state) => state.mode);
|
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() {
|
function handlePointerDown() {
|
||||||
window.addEventListener('pointermove', handlePointerMove);
|
window.addEventListener('pointermove', handlePointerMove);
|
||||||
@@ -49,23 +56,37 @@ export function TankDisplay({ duel }: TankDisplayProps) {
|
|||||||
setIsFullScreen(document.fullscreenElement !== null);
|
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);
|
document.addEventListener('fullscreenchange', handleFullScreenChange);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener('fullscreenchange', handleFullScreenChange);
|
document.removeEventListener('fullscreenchange', handleFullScreenChange);
|
||||||
|
poseEvent.off(handlePoseEvent);
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!duel.protagonist) return void 0;
|
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) => {
|
mutateTankopedia((draft) => {
|
||||||
[draft.model.physical.pitch, draft.model.physical.yaw] =
|
[draft.model.physical.pitch, draft.model.physical.yaw] =
|
||||||
applyPitchYawLimits(
|
applyPitchYawLimits(
|
||||||
@@ -116,7 +137,7 @@ export function TankDisplay({ duel }: TankDisplayProps) {
|
|||||||
camera={{ fov: 20 }}
|
camera={{ fov: 20 }}
|
||||||
onPointerDown={handlePointerDown}
|
onPointerDown={handlePointerDown}
|
||||||
>
|
>
|
||||||
<Controls />
|
<Controls duel={duel} />
|
||||||
<SceneProps />
|
<SceneProps />
|
||||||
<Lighting />
|
<Lighting />
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import {
|
import {
|
||||||
EnterFullScreenIcon,
|
EnterFullScreenIcon,
|
||||||
ExitFullScreenIcon,
|
ExitFullScreenIcon,
|
||||||
|
EyeOpenIcon,
|
||||||
GearIcon,
|
GearIcon,
|
||||||
} from '@radix-ui/react-icons';
|
} from '@radix-ui/react-icons';
|
||||||
import { Button, DropdownMenu, Flex } from '@radix-ui/themes';
|
import { Button, DropdownMenu, Flex } from '@radix-ui/themes';
|
||||||
import { RefObject } from 'react';
|
import { RefObject } from 'react';
|
||||||
|
import { Pose, poseEvent } from '../../../../../../../core/blitzkrieg/pose';
|
||||||
import mutateTankopedia, {
|
import mutateTankopedia, {
|
||||||
useTankopedia,
|
useTankopedia,
|
||||||
} from '../../../../../../../stores/tankopedia';
|
} from '../../../../../../../stores/tankopedia';
|
||||||
@@ -30,6 +32,25 @@ export function Options({ isFullScreen, canvas }: OptionsProps) {
|
|||||||
right: 18,
|
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.Root>
|
||||||
<DropdownMenu.Trigger>
|
<DropdownMenu.Trigger>
|
||||||
<Button color={showGrid ? 'purple' : 'gray'} variant="ghost">
|
<Button color={showGrid ? 'purple' : 'gray'} variant="ghost">
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { EventManager } from './eventManager';
|
||||||
|
|
||||||
|
export enum Pose {
|
||||||
|
HullDown,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const poseEvent = new EventManager<Pose>();
|
||||||
Reference in new issue
Block a user