feat: optimized state handling

This commit is contained in:
TrèsAbhi committed 2024-01-13 13:44:27 -06:00
1 parent 033d361cea
commit 230b2df5fd
6 files changed
+145 -186

No files matched your search

@@ -1,16 +1,4 @@
import {
EnterFullScreenIcon,
ExitFullScreenIcon,
GearIcon,
} from '@radix-ui/react-icons';
import {
Button,
Card,
DropdownMenu,
Flex,
Tabs,
Theme,
} from '@radix-ui/themes';
import { Card, Flex, Tabs, Theme } from '@radix-ui/themes';
import { Html } from '@react-three/drei';
import { Canvas } from '@react-three/fiber';
import { Suspense, use, useEffect, useRef, useState } from 'react';
@@ -26,6 +14,7 @@ import { Controls } from '../Control';
import { Lighting } from '../Lighting';
import { RotationInputs } from '../RotationInputs';
import { SceneProps } from '../SceneProps';
import { Options } from './components/Options';
import { TankArmor } from './components/TankArmor';
import { TankModel } from './components/TankModel';
@@ -35,8 +24,6 @@ export function TankDisplay() {
const canvasWrapper = useRef<HTMLDivElement>(null);
const [isFullScreen, setIsFullScreen] = useState(false);
const mode = useTankopedia((state) => state.mode);
const showGrid = useTankopedia((state) => state.model.showGrid);
const showSpacedArmor = useTankopedia((state) => state.model.showSpacedArmor);
const protagonist = useTankopedia((state) => {
if (!state.areTanksAssigned) return;
return state.protagonist;
@@ -143,68 +130,12 @@ export function TankDisplay() {
}
>
<TankModel />
{mode === 'armor' && <TankArmor />}
<TankArmor />
</Suspense>
</Canvas>
</div>
<Flex
gap="4"
style={{
position: 'absolute',
top: 18,
right: 18,
}}
>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
<Button color={showGrid ? 'purple' : 'gray'} variant="ghost">
<GearIcon />
</Button>
</DropdownMenu.Trigger>
<DropdownMenu.Content>
<DropdownMenu.CheckboxItem
checked={showGrid}
onCheckedChange={(checked) => {
mutateTankopedia((draft) => {
draft.model.showGrid = checked;
});
}}
>
Show grid
</DropdownMenu.CheckboxItem>
<DropdownMenu.CheckboxItem
checked={showSpacedArmor}
onCheckedChange={(checked) => {
mutateTankopedia((draft) => {
draft.model.showSpacedArmor = checked;
});
}}
>
Spaced armor
</DropdownMenu.CheckboxItem>
</DropdownMenu.Content>
</DropdownMenu.Root>
<Button
variant="ghost"
onClick={() => {
if (isFullScreen) {
document.exitFullscreen();
} else {
canvasWrapper.current?.requestFullscreen();
}
}}
>
{isFullScreen ? (
<ExitFullScreenIcon />
) : (
<EnterFullScreenIcon />
)}
</Button>
</Flex>
<Options canvas={canvasWrapper} isFullScreen={isFullScreen} />
<RotationInputs />
</Flex>
@@ -0,0 +1,76 @@
import {
EnterFullScreenIcon,
ExitFullScreenIcon,
GearIcon,
} from '@radix-ui/react-icons';
import { Button, DropdownMenu, Flex } from '@radix-ui/themes';
import { RefObject } from 'react';
import mutateTankopedia, {
useTankopedia,
} from '../../../../../../../stores/tankopedia';
interface OptionsProps {
isFullScreen: boolean;
canvas: RefObject<HTMLElement>;
}
export function Options({ isFullScreen, canvas }: OptionsProps) {
const showGrid = useTankopedia((state) => state.model.showGrid);
const showSpacedArmor = useTankopedia((state) => state.model.showSpacedArmor);
return (
<Flex
gap="4"
style={{
position: 'absolute',
top: 18,
right: 18,
}}
>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
<Button color={showGrid ? 'purple' : 'gray'} variant="ghost">
<GearIcon />
</Button>
</DropdownMenu.Trigger>
<DropdownMenu.Content>
<DropdownMenu.CheckboxItem
checked={showGrid}
onCheckedChange={(checked) => {
mutateTankopedia((draft) => {
draft.model.showGrid = checked;
});
}}
>
Show grid
</DropdownMenu.CheckboxItem>
<DropdownMenu.CheckboxItem
checked={showSpacedArmor}
onCheckedChange={(checked) => {
mutateTankopedia((draft) => {
draft.model.showSpacedArmor = checked;
});
}}
>
Spaced armor
</DropdownMenu.CheckboxItem>
</DropdownMenu.Content>
</DropdownMenu.Root>
<Button
variant="ghost"
onClick={() => {
if (isFullScreen) {
document.exitFullscreen();
} else {
canvas.current?.requestFullscreen();
}
}}
>
{isFullScreen ? <ExitFullScreenIcon /> : <EnterFullScreenIcon />}
</Button>
</Flex>
);
}
@@ -1,13 +1,10 @@
import { GroupProps, useLoader } from '@react-three/fiber';
import { useEffect, useState } from 'react';
import { Euler, Mesh, Vector3 } from 'three';
import { useEffect, useRef, useState } from 'react';
import { Euler, Group, Mesh, Vector3 } from 'three';
import { GLTFLoader } from 'three-stdlib';
import { ArmorMesh } from '../../../../../../../components/ArmorMesh';
import { HeadsUpDisplay } from '../../../../../../../components/HeadsUpDisplay';
import { X_AXIS } from '../../../../../../../constants/axis';
import { canSplash } from '../../../../../../../core/blitz/canSplash';
import { isExplosive } from '../../../../../../../core/blitz/isExplosive';
import { numericPenetration } from '../../../../../../../core/blitz/numericPenetration';
import { asset } from '../../../../../../../core/blitzkrieg/asset';
import {
ModelDefinitions,
@@ -21,7 +18,7 @@ import { Lighting } from '../../Lighting';
interface TankArmorProps extends GroupProps {}
export function TankArmor({ ...props }: TankArmorProps) {
const controlsEnabled = useTankopedia((state) => state.model.controlsEnabled);
const wrapper = useRef<Group>(null);
const [awaitedModelDefinitions, setAwaitedModelDefinitions] = useState<
ModelDefinitions | undefined
>(undefined);
@@ -29,10 +26,6 @@ export function TankArmor({ ...props }: TankArmorProps) {
if (!state.areTanksAssigned) return;
return state.protagonist;
});
const antagonist = useTankopedia((state) => {
if (!state.areTanksAssigned) return;
return state.antagonist;
});
const showSpacedArmor = useTankopedia((state) => state.model.showSpacedArmor);
const model = useTankopedia((state) => state.model);
@@ -42,9 +35,20 @@ export function TankArmor({ ...props }: TankArmorProps) {
})();
}, []);
useEffect(() => {
const unsubscribe = useTankopedia.subscribe(
(state) => state.model.controlsEnabled,
(controlsEnabled) => {
if (wrapper.current) wrapper.current.visible = controlsEnabled;
},
);
return unsubscribe;
});
// it's ok to have hooks after this early termination because this will never be true
// it's more for typescript to stop throwing a fit
if (!protagonist || !antagonist) return null;
if (!protagonist) return null;
const armorGltf = useLoader(
GLTFLoader,
@@ -77,11 +81,6 @@ export function TankArmor({ ...props }: TankArmorProps) {
.sub(turretOrigin)
.applyAxisAngle(new Vector3(0, 0, 1), model.turretYaw);
const turretRotation = new Euler(0, 0, model.turretYaw);
const splashCapable = canSplash(antagonist.shell.type);
const explosiveCapable = isExplosive(antagonist.shell.type);
const penetration = numericPenetration(antagonist.shell.penetration);
const ricochet = antagonist.shell.ricochet ?? Math.PI / 2;
const normalization = antagonist.shell.normalization ?? 0;
if (tankModelDefinition.turretRotation) {
const pitch = -tankModelDefinition.turretRotation.pitch * (Math.PI / 180);
@@ -99,25 +98,14 @@ export function TankArmor({ ...props }: TankArmorProps) {
turretPosition.add(turretOrigin);
// const tracksGeometry = mergeBufferGeometries(
// modelNodes
// .map((node) => {
// const isWheel = node.name.startsWith('chassis_wheel_');
// const isTrack = node.name.startsWith('chassis_track_');
// const isVisible = isWheel || isTrack;
// if (isVisible) return (node as Mesh).geometry;
// })
// .filter(Boolean),
// );
return (
<HeadsUpDisplay>
<Lighting />
<group
{...props}
ref={wrapper}
rotation={[-Math.PI / 2, 0, model.hullYaw]}
visible={controlsEnabled}
>
{armorNodes.map((node) => {
const isHull = node.name.startsWith('hull_');
@@ -139,31 +127,12 @@ export function TankArmor({ ...props }: TankArmorProps) {
<ArmorMesh
key={node.uuid}
geometry={(node as Mesh).geometry}
caliber={antagonist.shell.caliber}
canSplash={splashCapable}
isExplosive={explosiveCapable}
isSpaced={spaced ?? false}
normalization={normalization}
penetration={penetration}
thickness={thickness}
ricochet={ricochet}
/>
);
})}
{/* <ArmorMesh
geometry={tracksGeometry!}
caliber={antagonist.shell.caliber}
canSplash={splashCapable}
isExplosive={explosiveCapable}
isSpaced
normalization={normalization}
penetration={penetration}
thickness={50}
ricochet={ricochet}
isExternalModule
/> */}
{modelNodes.map((node) => {
const isWheel = node.name.startsWith('chassis_wheel_');
const isTrack = node.name.startsWith('chassis_track_');
@@ -176,14 +145,8 @@ export function TankArmor({ ...props }: TankArmorProps) {
<ArmorMesh
key={node.uuid}
geometry={(node as Mesh).geometry}
caliber={antagonist.shell.caliber}
canSplash={splashCapable}
isExplosive={explosiveCapable}
isSpaced
normalization={normalization}
penetration={penetration}
thickness={thickness}
ricochet={ricochet}
isExternalModule
/>
);
@@ -215,14 +178,8 @@ export function TankArmor({ ...props }: TankArmorProps) {
key={node.uuid}
geometry={(node as Mesh).geometry}
position={turretOrigin}
caliber={antagonist.shell.caliber}
canSplash={splashCapable}
isExplosive={explosiveCapable}
isSpaced={spaced ?? false}
normalization={normalization}
penetration={penetration}
thickness={thickness}
ricochet={ricochet}
/>
);
})}
@@ -258,14 +215,8 @@ export function TankArmor({ ...props }: TankArmorProps) {
key={node.uuid}
geometry={(node as Mesh).geometry}
position={turretOrigin.clone().add(gunOrigin)}
caliber={antagonist.shell.caliber}
canSplash={splashCapable}
isExplosive={explosiveCapable}
isSpaced={spaced ?? false}
normalization={normalization}
penetration={penetration}
thickness={thickness}
ricochet={ricochet}
/>
);
})}
@@ -283,14 +234,8 @@ export function TankArmor({ ...props }: TankArmorProps) {
<ArmorMesh
key={node.uuid}
geometry={(node as Mesh).geometry}
caliber={antagonist.shell.caliber}
canSplash={splashCapable}
isExplosive={explosiveCapable}
isSpaced
normalization={normalization}
penetration={penetration}
thickness={thickness}
ricochet={ricochet}
isExternalModule
/>
);