mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-24 20:19:49 +02:00
feat: optimized state handling x2
This commit is contained in:
1 parent
230b2df5fd
commit
586cd84349
13 files changed
+138
-149
No files matched your search
@@ -17,7 +17,6 @@ import {
|
|||||||
} from '@radix-ui/themes';
|
} from '@radix-ui/themes';
|
||||||
import { go } from 'fuzzysort';
|
import { go } from 'fuzzysort';
|
||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
import { useRouter } from 'next/navigation';
|
|
||||||
import { use, useRef, useState } from 'react';
|
import { use, useRef, useState } from 'react';
|
||||||
import { ModuleButton } from '../../../../../components/ModuleButton';
|
import { ModuleButton } from '../../../../../components/ModuleButton';
|
||||||
import { SmallTankIcon } from '../../../../../components/SmallTankIcon';
|
import { SmallTankIcon } from '../../../../../components/SmallTankIcon';
|
||||||
@@ -31,7 +30,6 @@ import mutateTankopedia, {
|
|||||||
} from '../../../../../stores/tankopedia';
|
} from '../../../../../stores/tankopedia';
|
||||||
|
|
||||||
export function AntagonistBar() {
|
export function AntagonistBar() {
|
||||||
const router = useRouter();
|
|
||||||
const awaitedTankDefinitions = use(tankDefinitions);
|
const awaitedTankDefinitions = use(tankDefinitions);
|
||||||
const mode = useTankopedia((state) => state.mode);
|
const mode = useTankopedia((state) => state.mode);
|
||||||
const awaitedTankNamesDiacritics = use(tankNamesDiacritics);
|
const awaitedTankNamesDiacritics = use(tankNamesDiacritics);
|
||||||
|
|||||||
@@ -7,17 +7,25 @@ import { useTankopedia } from '../../../../../stores/tankopedia';
|
|||||||
export function Controls() {
|
export function Controls() {
|
||||||
const camera = useThree((state) => state.camera);
|
const camera = useThree((state) => state.camera);
|
||||||
const orbitControls = useRef<OrbitControlsClass>(null);
|
const orbitControls = useRef<OrbitControlsClass>(null);
|
||||||
const enabled = useTankopedia((state) => state.model.controlsEnabled);
|
|
||||||
|
|
||||||
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(
|
||||||
|
(state) => state.model.visual.controlsEnabled,
|
||||||
|
(enabled) => {
|
||||||
|
if (orbitControls.current) orbitControls.current.enabled = enabled;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return unsubscribe;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<OrbitControls
|
<OrbitControls
|
||||||
ref={orbitControls}
|
ref={orbitControls}
|
||||||
enabled={enabled}
|
enabled={useTankopedia.getState().model.visual.controlsEnabled}
|
||||||
rotateSpeed={0.25}
|
rotateSpeed={0.25}
|
||||||
enableDamping={false}
|
enableDamping={false}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Card, Flex, Tabs, Theme } from '@radix-ui/themes';
|
import { Card, Flex, Tabs, Theme } from '@radix-ui/themes';
|
||||||
import { Html } from '@react-three/drei';
|
import { Html } from '@react-three/drei';
|
||||||
import { Canvas } from '@react-three/fiber';
|
import { Canvas } from '@react-three/fiber';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
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';
|
||||||
@@ -19,6 +20,7 @@ import { TankArmor } from './components/TankArmor';
|
|||||||
import { TankModel } from './components/TankModel';
|
import { TankModel } from './components/TankModel';
|
||||||
|
|
||||||
export function TankDisplay() {
|
export function TankDisplay() {
|
||||||
|
const router = useRouter();
|
||||||
const canvas = useRef<HTMLCanvasElement>(null);
|
const canvas = useRef<HTMLCanvasElement>(null);
|
||||||
const awaitedModelDefinitions = use(modelDefinitions);
|
const awaitedModelDefinitions = use(modelDefinitions);
|
||||||
const canvasWrapper = useRef<HTMLDivElement>(null);
|
const canvasWrapper = useRef<HTMLDivElement>(null);
|
||||||
@@ -64,15 +66,24 @@ export function TankDisplay() {
|
|||||||
const gunModelDefinition = turretModelDefinition.guns[protagonist.gun.id];
|
const gunModelDefinition = turretModelDefinition.guns[protagonist.gun.id];
|
||||||
|
|
||||||
mutateTankopedia((draft) => {
|
mutateTankopedia((draft) => {
|
||||||
[draft.model.gunPitch, draft.model.turretYaw] = applyPitchYawLimits(
|
[draft.model.physical.gunPitch, draft.model.physical.turretYaw] =
|
||||||
draft.model.gunPitch,
|
applyPitchYawLimits(
|
||||||
draft.model.turretYaw,
|
draft.model.physical.gunPitch,
|
||||||
gunModelDefinition.pitch,
|
draft.model.physical.turretYaw,
|
||||||
turretModelDefinition.yaw,
|
gunModelDefinition.pitch,
|
||||||
);
|
turretModelDefinition.yaw,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}, [protagonist?.gun, protagonist?.turret]);
|
}, [protagonist?.gun, protagonist?.turret]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (location.hash.startsWith('#')) {
|
||||||
|
mutateTankopedia((draft) => {
|
||||||
|
draft.mode = location.hash.match(/#(.+)/)![1] as TankopediaMode;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
if (!protagonist) return null;
|
if (!protagonist) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -96,6 +107,8 @@ export function TankDisplay() {
|
|||||||
<Tabs.Root
|
<Tabs.Root
|
||||||
value={mode}
|
value={mode}
|
||||||
onValueChange={(mode) => {
|
onValueChange={(mode) => {
|
||||||
|
router.push(`#${mode}`);
|
||||||
|
|
||||||
mutateTankopedia((draft) => {
|
mutateTankopedia((draft) => {
|
||||||
draft.mode = mode as TankopediaMode;
|
draft.mode = mode as TankopediaMode;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -15,8 +15,10 @@ interface OptionsProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function Options({ isFullScreen, canvas }: OptionsProps) {
|
export function Options({ isFullScreen, canvas }: OptionsProps) {
|
||||||
const showGrid = useTankopedia((state) => state.model.showGrid);
|
const showGrid = useTankopedia((state) => state.model.visual.showGrid);
|
||||||
const showSpacedArmor = useTankopedia((state) => state.model.showSpacedArmor);
|
const showSpacedArmor = useTankopedia(
|
||||||
|
(state) => state.model.visual.showSpacedArmor,
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
@@ -39,7 +41,7 @@ export function Options({ isFullScreen, canvas }: OptionsProps) {
|
|||||||
checked={showGrid}
|
checked={showGrid}
|
||||||
onCheckedChange={(checked) => {
|
onCheckedChange={(checked) => {
|
||||||
mutateTankopedia((draft) => {
|
mutateTankopedia((draft) => {
|
||||||
draft.model.showGrid = checked;
|
draft.model.visual.showGrid = checked;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -50,7 +52,7 @@ export function Options({ isFullScreen, canvas }: OptionsProps) {
|
|||||||
checked={showSpacedArmor}
|
checked={showSpacedArmor}
|
||||||
onCheckedChange={(checked) => {
|
onCheckedChange={(checked) => {
|
||||||
mutateTankopedia((draft) => {
|
mutateTankopedia((draft) => {
|
||||||
draft.model.showSpacedArmor = checked;
|
draft.model.visual.showSpacedArmor = checked;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -13,11 +13,11 @@ import {
|
|||||||
import { nameToArmorId } from '../../../../../../../core/blitzkrieg/nameToArmorId';
|
import { nameToArmorId } from '../../../../../../../core/blitzkrieg/nameToArmorId';
|
||||||
import { resolveArmor } from '../../../../../../../core/blitzkrieg/resolveThickness';
|
import { resolveArmor } from '../../../../../../../core/blitzkrieg/resolveThickness';
|
||||||
import { useTankopedia } from '../../../../../../../stores/tankopedia';
|
import { useTankopedia } from '../../../../../../../stores/tankopedia';
|
||||||
import { Lighting } from '../../Lighting';
|
|
||||||
|
|
||||||
interface TankArmorProps extends GroupProps {}
|
interface TankArmorProps extends GroupProps {}
|
||||||
|
|
||||||
export function TankArmor({ ...props }: TankArmorProps) {
|
export function TankArmor({ ...props }: TankArmorProps) {
|
||||||
|
const mode = useTankopedia((state) => state.mode);
|
||||||
const wrapper = useRef<Group>(null);
|
const wrapper = useRef<Group>(null);
|
||||||
const [awaitedModelDefinitions, setAwaitedModelDefinitions] = useState<
|
const [awaitedModelDefinitions, setAwaitedModelDefinitions] = useState<
|
||||||
ModelDefinitions | undefined
|
ModelDefinitions | undefined
|
||||||
@@ -26,8 +26,10 @@ export function TankArmor({ ...props }: TankArmorProps) {
|
|||||||
if (!state.areTanksAssigned) return;
|
if (!state.areTanksAssigned) return;
|
||||||
return state.protagonist;
|
return state.protagonist;
|
||||||
});
|
});
|
||||||
const showSpacedArmor = useTankopedia((state) => state.model.showSpacedArmor);
|
const showSpacedArmor = useTankopedia(
|
||||||
const model = useTankopedia((state) => state.model);
|
(state) => state.model.visual.showSpacedArmor,
|
||||||
|
);
|
||||||
|
const physical = useTankopedia((state) => state.model.physical);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
@@ -35,17 +37,6 @@ 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 ok to have hooks after this early termination because this will never be true
|
||||||
// it's more for typescript to stop throwing a fit
|
// it's more for typescript to stop throwing a fit
|
||||||
if (!protagonist) return null;
|
if (!protagonist) return null;
|
||||||
@@ -79,8 +70,8 @@ export function TankArmor({ ...props }: TankArmorProps) {
|
|||||||
).applyAxisAngle(X_AXIS, Math.PI / 2);
|
).applyAxisAngle(X_AXIS, Math.PI / 2);
|
||||||
const turretPosition = new Vector3()
|
const turretPosition = new Vector3()
|
||||||
.sub(turretOrigin)
|
.sub(turretOrigin)
|
||||||
.applyAxisAngle(new Vector3(0, 0, 1), model.turretYaw);
|
.applyAxisAngle(new Vector3(0, 0, 1), physical.turretYaw);
|
||||||
const turretRotation = new Euler(0, 0, model.turretYaw);
|
const turretRotation = new Euler(0, 0, physical.turretYaw);
|
||||||
|
|
||||||
if (tankModelDefinition.turretRotation) {
|
if (tankModelDefinition.turretRotation) {
|
||||||
const pitch = -tankModelDefinition.turretRotation.pitch * (Math.PI / 180);
|
const pitch = -tankModelDefinition.turretRotation.pitch * (Math.PI / 180);
|
||||||
@@ -100,12 +91,11 @@ export function TankArmor({ ...props }: TankArmorProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<HeadsUpDisplay>
|
<HeadsUpDisplay>
|
||||||
<Lighting />
|
|
||||||
|
|
||||||
<group
|
<group
|
||||||
{...props}
|
{...props}
|
||||||
|
visible={mode === 'armor'}
|
||||||
ref={wrapper}
|
ref={wrapper}
|
||||||
rotation={[-Math.PI / 2, 0, model.hullYaw]}
|
rotation={[-Math.PI / 2, 0, physical.hullYaw]}
|
||||||
>
|
>
|
||||||
{armorNodes.map((node) => {
|
{armorNodes.map((node) => {
|
||||||
const isHull = node.name.startsWith('hull_');
|
const isHull = node.name.startsWith('hull_');
|
||||||
@@ -187,10 +177,10 @@ export function TankArmor({ ...props }: TankArmorProps) {
|
|||||||
position={new Vector3()
|
position={new Vector3()
|
||||||
.sub(turretOrigin)
|
.sub(turretOrigin)
|
||||||
.sub(gunOrigin)
|
.sub(gunOrigin)
|
||||||
.applyAxisAngle(new Vector3(1, 0, 0), model.gunPitch)
|
.applyAxisAngle(new Vector3(1, 0, 0), physical.gunPitch)
|
||||||
.add(turretOrigin)
|
.add(turretOrigin)
|
||||||
.add(gunOrigin)}
|
.add(gunOrigin)}
|
||||||
rotation={[model.gunPitch, 0, 0]}
|
rotation={[physical.gunPitch, 0, 0]}
|
||||||
>
|
>
|
||||||
{armorNodes.map((node) => {
|
{armorNodes.map((node) => {
|
||||||
const isCurrentGun = node.name.startsWith(
|
const isCurrentGun = node.name.startsWith(
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export function TankModel() {
|
|||||||
if (!state.areTanksAssigned) return;
|
if (!state.areTanksAssigned) return;
|
||||||
return state.protagonist;
|
return state.protagonist;
|
||||||
});
|
});
|
||||||
const model = useTankopedia((state) => state.model);
|
const physical = useTankopedia((state) => state.model.physical);
|
||||||
const hullContainer = useRef<Group>(null);
|
const hullContainer = useRef<Group>(null);
|
||||||
const turretContainer = useRef<Group>(null);
|
const turretContainer = useRef<Group>(null);
|
||||||
const gunContainer = useRef<Group>(null);
|
const gunContainer = useRef<Group>(null);
|
||||||
@@ -70,8 +70,8 @@ export function TankModel() {
|
|||||||
).applyAxisAngle(X_AXIS, Math.PI / 2);
|
).applyAxisAngle(X_AXIS, Math.PI / 2);
|
||||||
const turretPosition = new Vector3()
|
const turretPosition = new Vector3()
|
||||||
.sub(turretOrigin)
|
.sub(turretOrigin)
|
||||||
.applyAxisAngle(new Vector3(0, 0, 1), model.turretYaw);
|
.applyAxisAngle(new Vector3(0, 0, 1), physical.turretYaw);
|
||||||
const turretRotation = new Euler(0, 0, model.turretYaw);
|
const turretRotation = new Euler(0, 0, physical.turretYaw);
|
||||||
|
|
||||||
if (tankModelDefinition.turretRotation) {
|
if (tankModelDefinition.turretRotation) {
|
||||||
const pitch = -tankModelDefinition.turretRotation.pitch * (Math.PI / 180);
|
const pitch = -tankModelDefinition.turretRotation.pitch * (Math.PI / 180);
|
||||||
@@ -90,7 +90,7 @@ export function TankModel() {
|
|||||||
turretPosition.add(turretOrigin);
|
turretPosition.add(turretOrigin);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<group ref={hullContainer} rotation={[-Math.PI / 2, 0, model.hullYaw]}>
|
<group ref={hullContainer} rotation={[-Math.PI / 2, 0, physical.hullYaw]}>
|
||||||
{nodes.map((node) => {
|
{nodes.map((node) => {
|
||||||
const isHull = node.name === 'hull';
|
const isHull = node.name === 'hull';
|
||||||
const isWheel = node.name.startsWith('chassis_wheel_');
|
const isWheel = node.name.startsWith('chassis_wheel_');
|
||||||
@@ -101,11 +101,11 @@ export function TankModel() {
|
|||||||
function handlePointerDown(event: ThreeEvent<PointerEvent>) {
|
function handlePointerDown(event: ThreeEvent<PointerEvent>) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
mutateTankopedia((draft) => {
|
mutateTankopedia((draft) => {
|
||||||
draft.model.controlsEnabled = false;
|
draft.model.visual.controlsEnabled = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isHull) {
|
if (isHull) {
|
||||||
draftHullYaw = model.hullYaw;
|
draftHullYaw = physical.hullYaw;
|
||||||
|
|
||||||
window.addEventListener('pointermove', handlePointerMoveHull);
|
window.addEventListener('pointermove', handlePointerMoveHull);
|
||||||
window.addEventListener('pointerup', handlePointerUpHull);
|
window.addEventListener('pointerup', handlePointerUpHull);
|
||||||
@@ -122,8 +122,8 @@ export function TankModel() {
|
|||||||
}
|
}
|
||||||
function handlePointerUpHull() {
|
function handlePointerUpHull() {
|
||||||
mutateTankopedia((draft) => {
|
mutateTankopedia((draft) => {
|
||||||
draft.model.controlsEnabled = true;
|
draft.model.visual.controlsEnabled = true;
|
||||||
draft.model.hullYaw = normalizeAnglePI(draftHullYaw);
|
draft.model.physical.hullYaw = normalizeAnglePI(draftHullYaw);
|
||||||
});
|
});
|
||||||
window.removeEventListener('pointermove', handlePointerMoveHull);
|
window.removeEventListener('pointermove', handlePointerMoveHull);
|
||||||
window.removeEventListener('pointerup', handlePointerUpHull);
|
window.removeEventListener('pointerup', handlePointerUpHull);
|
||||||
@@ -143,7 +143,7 @@ export function TankModel() {
|
|||||||
}
|
}
|
||||||
function handlePointerUpTrack() {
|
function handlePointerUpTrack() {
|
||||||
mutateTankopedia((draft) => {
|
mutateTankopedia((draft) => {
|
||||||
draft.model.controlsEnabled = true;
|
draft.model.visual.controlsEnabled = true;
|
||||||
});
|
});
|
||||||
window.removeEventListener('pointermove', handlePointerMoveTrack);
|
window.removeEventListener('pointermove', handlePointerMoveTrack);
|
||||||
window.removeEventListener('pointerup', handlePointerUpTrack);
|
window.removeEventListener('pointerup', handlePointerUpTrack);
|
||||||
@@ -185,11 +185,11 @@ export function TankModel() {
|
|||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
if (isTurret) {
|
if (isTurret) {
|
||||||
draftYaw = model.turretYaw;
|
draftYaw = physical.turretYaw;
|
||||||
draftPitch = model.gunPitch;
|
draftPitch = physical.gunPitch;
|
||||||
|
|
||||||
mutateTankopedia((draft) => {
|
mutateTankopedia((draft) => {
|
||||||
draft.model.controlsEnabled = false;
|
draft.model.visual.controlsEnabled = false;
|
||||||
});
|
});
|
||||||
window.addEventListener('pointermove', handlePointerMove);
|
window.addEventListener('pointermove', handlePointerMove);
|
||||||
window.addEventListener('pointerup', handlePointerUp);
|
window.addEventListener('pointerup', handlePointerUp);
|
||||||
@@ -240,9 +240,9 @@ export function TankModel() {
|
|||||||
}
|
}
|
||||||
function handlePointerUp() {
|
function handlePointerUp() {
|
||||||
mutateTankopedia((state) => {
|
mutateTankopedia((state) => {
|
||||||
state.model.controlsEnabled = true;
|
state.model.visual.controlsEnabled = true;
|
||||||
state.model.gunPitch = normalizeAnglePI(draftPitch);
|
state.model.physical.gunPitch = normalizeAnglePI(draftPitch);
|
||||||
state.model.turretYaw = normalizeAnglePI(draftYaw);
|
state.model.physical.turretYaw = normalizeAnglePI(draftYaw);
|
||||||
});
|
});
|
||||||
window.removeEventListener('pointermove', handlePointerMove);
|
window.removeEventListener('pointermove', handlePointerMove);
|
||||||
window.removeEventListener('pointerup', handlePointerUp);
|
window.removeEventListener('pointerup', handlePointerUp);
|
||||||
@@ -271,10 +271,10 @@ export function TankModel() {
|
|||||||
position={new Vector3()
|
position={new Vector3()
|
||||||
.sub(turretOrigin)
|
.sub(turretOrigin)
|
||||||
.sub(gunOrigin)
|
.sub(gunOrigin)
|
||||||
.applyAxisAngle(new Vector3(1, 0, 0), model.gunPitch)
|
.applyAxisAngle(new Vector3(1, 0, 0), physical.gunPitch)
|
||||||
.add(turretOrigin)
|
.add(turretOrigin)
|
||||||
.add(gunOrigin)}
|
.add(gunOrigin)}
|
||||||
rotation={[model.gunPitch, 0, 0]}
|
rotation={[physical.gunPitch, 0, 0]}
|
||||||
ref={gunContainer}
|
ref={gunContainer}
|
||||||
>
|
>
|
||||||
{nodes.map((node) => {
|
{nodes.map((node) => {
|
||||||
@@ -294,10 +294,10 @@ export function TankModel() {
|
|||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
mutateTankopedia((draft) => {
|
mutateTankopedia((draft) => {
|
||||||
draft.model.controlsEnabled = false;
|
draft.model.visual.controlsEnabled = false;
|
||||||
});
|
});
|
||||||
draftPitch = model.gunPitch;
|
draftPitch = physical.gunPitch;
|
||||||
draftYaw = model.turretYaw;
|
draftYaw = physical.turretYaw;
|
||||||
window.addEventListener('pointermove', handlePointerMove);
|
window.addEventListener('pointermove', handlePointerMove);
|
||||||
window.addEventListener('pointerup', handlePointerUp);
|
window.addEventListener('pointerup', handlePointerUp);
|
||||||
}
|
}
|
||||||
@@ -346,9 +346,9 @@ export function TankModel() {
|
|||||||
}
|
}
|
||||||
function handlePointerUp() {
|
function handlePointerUp() {
|
||||||
mutateTankopedia((state) => {
|
mutateTankopedia((state) => {
|
||||||
state.model.controlsEnabled = true;
|
state.model.visual.controlsEnabled = true;
|
||||||
state.model.gunPitch = normalizeAnglePI(draftPitch);
|
state.model.physical.gunPitch = normalizeAnglePI(draftPitch);
|
||||||
state.model.turretYaw = normalizeAnglePI(draftYaw);
|
state.model.physical.turretYaw = normalizeAnglePI(draftYaw);
|
||||||
});
|
});
|
||||||
window.removeEventListener('pointermove', handlePointerMove);
|
window.removeEventListener('pointermove', handlePointerMove);
|
||||||
window.removeEventListener('pointerup', handlePointerUp);
|
window.removeEventListener('pointerup', handlePointerUp);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import mutateTankopedia, {
|
|||||||
} from '../../../../../stores/tankopedia';
|
} from '../../../../../stores/tankopedia';
|
||||||
|
|
||||||
export function RotationInputs() {
|
export function RotationInputs() {
|
||||||
const model = useTankopedia((state) => state.model);
|
const physical = useTankopedia((state) => state.model.physical);
|
||||||
const awaitedModelDefinitions = use(modelDefinitions);
|
const awaitedModelDefinitions = use(modelDefinitions);
|
||||||
const turretYawInput = useRef<HTMLInputElement>(null);
|
const turretYawInput = useRef<HTMLInputElement>(null);
|
||||||
const hullYawInput = useRef<HTMLInputElement>(null);
|
const hullYawInput = useRef<HTMLInputElement>(null);
|
||||||
@@ -20,19 +20,19 @@ export function RotationInputs() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
hullYawInput.current!.value = `${-Math.round(
|
hullYawInput.current!.value = `${-Math.round(
|
||||||
model.hullYaw * (180 / Math.PI),
|
physical.hullYaw * (180 / Math.PI),
|
||||||
)}`;
|
)}`;
|
||||||
}, [model.hullYaw]);
|
}, [physical.hullYaw]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
turretYawInput.current!.value = `${-Math.round(
|
turretYawInput.current!.value = `${-Math.round(
|
||||||
model.turretYaw * (180 / Math.PI),
|
physical.turretYaw * (180 / Math.PI),
|
||||||
)}`;
|
)}`;
|
||||||
}, [model.turretYaw]);
|
}, [physical.turretYaw]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
gunPitchInput.current!.value = `${-Math.round(
|
gunPitchInput.current!.value = `${-Math.round(
|
||||||
model.gunPitch * (180 / Math.PI),
|
physical.gunPitch * (180 / Math.PI),
|
||||||
)}`;
|
)}`;
|
||||||
}, [model.gunPitch]);
|
}, [physical.gunPitch]);
|
||||||
|
|
||||||
if (!protagonist) return null;
|
if (!protagonist) return null;
|
||||||
|
|
||||||
@@ -65,13 +65,13 @@ export function RotationInputs() {
|
|||||||
>
|
>
|
||||||
<TextField.Slot>Hull</TextField.Slot>
|
<TextField.Slot>Hull</TextField.Slot>
|
||||||
<TextField.Input
|
<TextField.Input
|
||||||
defaultValue={Math.round(model.hullYaw * (180 / Math.PI))}
|
defaultValue={Math.round(physical.hullYaw * (180 / Math.PI))}
|
||||||
onBlur={() => {
|
onBlur={() => {
|
||||||
const normalized = normalizeAnglePI(
|
const normalized = normalizeAnglePI(
|
||||||
-Number(hullYawInput.current?.value) * (Math.PI / 180),
|
-Number(hullYawInput.current?.value) * (Math.PI / 180),
|
||||||
);
|
);
|
||||||
mutateTankopedia((state) => {
|
mutateTankopedia((state) => {
|
||||||
state.model.hullYaw = normalized;
|
state.model.physical.hullYaw = normalized;
|
||||||
});
|
});
|
||||||
hullYawInput.current!.value = `${Math.round(normalized)}`;
|
hullYawInput.current!.value = `${Math.round(normalized)}`;
|
||||||
}}
|
}}
|
||||||
@@ -94,17 +94,17 @@ export function RotationInputs() {
|
|||||||
>
|
>
|
||||||
<TextField.Slot>Turret</TextField.Slot>
|
<TextField.Slot>Turret</TextField.Slot>
|
||||||
<TextField.Input
|
<TextField.Input
|
||||||
defaultValue={Math.round(model.turretYaw * (180 / Math.PI))}
|
defaultValue={Math.round(physical.turretYaw * (180 / Math.PI))}
|
||||||
onBlur={() => {
|
onBlur={() => {
|
||||||
const [pitch, yaw] = applyPitchYawLimits(
|
const [pitch, yaw] = applyPitchYawLimits(
|
||||||
model.gunPitch,
|
physical.gunPitch,
|
||||||
-Number(turretYawInput.current?.value) * (Math.PI / 180),
|
-Number(turretYawInput.current?.value) * (Math.PI / 180),
|
||||||
gunModelDefinition.pitch,
|
gunModelDefinition.pitch,
|
||||||
turretModelDefinition.yaw,
|
turretModelDefinition.yaw,
|
||||||
);
|
);
|
||||||
mutateTankopedia((state) => {
|
mutateTankopedia((state) => {
|
||||||
state.model.gunPitch = pitch;
|
state.model.physical.gunPitch = pitch;
|
||||||
state.model.turretYaw = yaw;
|
state.model.physical.turretYaw = yaw;
|
||||||
});
|
});
|
||||||
turretYawInput.current!.value = `${Math.round(
|
turretYawInput.current!.value = `${Math.round(
|
||||||
yaw * (180 / Math.PI),
|
yaw * (180 / Math.PI),
|
||||||
@@ -129,17 +129,17 @@ export function RotationInputs() {
|
|||||||
>
|
>
|
||||||
<TextField.Slot>Gun</TextField.Slot>
|
<TextField.Slot>Gun</TextField.Slot>
|
||||||
<TextField.Input
|
<TextField.Input
|
||||||
defaultValue={-Math.round(model.gunPitch * (180 / Math.PI))}
|
defaultValue={-Math.round(physical.gunPitch * (180 / Math.PI))}
|
||||||
onBlur={() => {
|
onBlur={() => {
|
||||||
const [pitch, yaw] = applyPitchYawLimits(
|
const [pitch, yaw] = applyPitchYawLimits(
|
||||||
-Number(gunPitchInput.current?.value) * (Math.PI / 180),
|
-Number(gunPitchInput.current?.value) * (Math.PI / 180),
|
||||||
model.turretYaw,
|
physical.turretYaw,
|
||||||
gunModelDefinition.pitch,
|
gunModelDefinition.pitch,
|
||||||
turretModelDefinition.yaw,
|
turretModelDefinition.yaw,
|
||||||
);
|
);
|
||||||
mutateTankopedia((state) => {
|
mutateTankopedia((state) => {
|
||||||
state.model.gunPitch = pitch;
|
state.model.physical.gunPitch = pitch;
|
||||||
state.model.turretYaw = yaw;
|
state.model.physical.turretYaw = yaw;
|
||||||
});
|
});
|
||||||
gunPitchInput.current!.value = `${Math.round(
|
gunPitchInput.current!.value = `${Math.round(
|
||||||
pitch * (180 / Math.PI),
|
pitch * (180 / Math.PI),
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import InfiniteGridHelper from '../../../../../components/InfiniteGridHelper';
|
|||||||
import { useTankopedia } from '../../../../../stores/tankopedia';
|
import { useTankopedia } from '../../../../../stores/tankopedia';
|
||||||
|
|
||||||
export function SceneProps() {
|
export function SceneProps() {
|
||||||
const showGrid = useTankopedia((state) => state.model.showGrid);
|
const showGrid = useTankopedia((state) => state.model.visual.showGrid);
|
||||||
|
|
||||||
if (!showGrid) return null;
|
if (!showGrid) return null;
|
||||||
|
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ export default function Page({ params }: { params: { id: string } }) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
mutateTankopedia((draft) => {
|
mutateTankopedia((draft) => {
|
||||||
draft.model.hullYaw = 0;
|
draft.model.physical.hullYaw = 0;
|
||||||
draft.model.turretYaw = 0;
|
draft.model.physical.turretYaw = 0;
|
||||||
draft.model.gunPitch = 0;
|
draft.model.physical.gunPitch = 0;
|
||||||
draft.mode = 'model';
|
draft.mode = 'model';
|
||||||
draft.areTanksAssigned = true;
|
draft.areTanksAssigned = true;
|
||||||
|
|
||||||
@@ -67,7 +67,6 @@ export default function Page({ params }: { params: { id: string } }) {
|
|||||||
}, [id]);
|
}, [id]);
|
||||||
|
|
||||||
const protagonist = useTankopedia((state) => {
|
const protagonist = useTankopedia((state) => {
|
||||||
// goofy ahh typescript discriminator hack
|
|
||||||
if (!state.areTanksAssigned) return;
|
if (!state.areTanksAssigned) return;
|
||||||
return state.protagonist;
|
return state.protagonist;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import * as styles from './index.css';
|
|||||||
|
|
||||||
export function Options() {
|
export function Options() {
|
||||||
const nations = use(NATIONS);
|
const nations = use(NATIONS);
|
||||||
const tankopediaState = useTankopedia();
|
const filters = useTankopedia((state) => state.filters);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex justify="center" direction="column" gap="2">
|
<Flex justify="center" direction="column" gap="2">
|
||||||
@@ -54,7 +54,7 @@ export function Options() {
|
|||||||
</DropdownMenu.Trigger>
|
</DropdownMenu.Trigger>
|
||||||
<DropdownMenu.Content>
|
<DropdownMenu.Content>
|
||||||
<DropdownMenu.RadioGroup
|
<DropdownMenu.RadioGroup
|
||||||
value={tankopediaState.filters.test}
|
value={filters.test}
|
||||||
onValueChange={(value) =>
|
onValueChange={(value) =>
|
||||||
mutateTankopedia((draft) => {
|
mutateTankopedia((draft) => {
|
||||||
draft.filters.test = value as TankopediaTestTankDisplay;
|
draft.filters.test = value as TankopediaTestTankDisplay;
|
||||||
@@ -75,11 +75,7 @@ export function Options() {
|
|||||||
{TREE_TYPES.map((type, index) => (
|
{TREE_TYPES.map((type, index) => (
|
||||||
<Button
|
<Button
|
||||||
key={type}
|
key={type}
|
||||||
variant={
|
variant={filters.treeTypes.includes(type) ? 'solid' : 'soft'}
|
||||||
tankopediaState.filters.treeTypes.includes(type)
|
|
||||||
? 'solid'
|
|
||||||
: 'soft'
|
|
||||||
}
|
|
||||||
style={{
|
style={{
|
||||||
flex: 1,
|
flex: 1,
|
||||||
margin: -0.5,
|
margin: -0.5,
|
||||||
@@ -113,9 +109,7 @@ export function Options() {
|
|||||||
{[...TANK_TYPES].reverse().map((type, index) => (
|
{[...TANK_TYPES].reverse().map((type, index) => (
|
||||||
<Button
|
<Button
|
||||||
key={type}
|
key={type}
|
||||||
variant={
|
variant={filters.types.includes(type) ? 'solid' : 'soft'}
|
||||||
tankopediaState.filters.types.includes(type) ? 'solid' : 'soft'
|
|
||||||
}
|
|
||||||
style={{
|
style={{
|
||||||
flex: 1,
|
flex: 1,
|
||||||
margin: -0.5,
|
margin: -0.5,
|
||||||
@@ -157,11 +151,7 @@ export function Options() {
|
|||||||
.map((tier, index) => (
|
.map((tier, index) => (
|
||||||
<Button
|
<Button
|
||||||
key={tier}
|
key={tier}
|
||||||
variant={
|
variant={filters.tiers.includes(tier) ? 'solid' : 'soft'}
|
||||||
tankopediaState.filters.tiers.includes(tier)
|
|
||||||
? 'solid'
|
|
||||||
: 'soft'
|
|
||||||
}
|
|
||||||
style={{
|
style={{
|
||||||
flex: 1,
|
flex: 1,
|
||||||
margin: -0.5,
|
margin: -0.5,
|
||||||
@@ -196,11 +186,7 @@ export function Options() {
|
|||||||
.map((tier, index) => (
|
.map((tier, index) => (
|
||||||
<Button
|
<Button
|
||||||
key={tier}
|
key={tier}
|
||||||
variant={
|
variant={filters.tiers.includes(tier) ? 'solid' : 'soft'}
|
||||||
tankopediaState.filters.tiers.includes(tier)
|
|
||||||
? 'solid'
|
|
||||||
: 'soft'
|
|
||||||
}
|
|
||||||
style={{
|
style={{
|
||||||
flex: 1,
|
flex: 1,
|
||||||
margin: -0.5,
|
margin: -0.5,
|
||||||
@@ -238,11 +224,7 @@ export function Options() {
|
|||||||
{nations.slice(0, 5).map((nation, index) => (
|
{nations.slice(0, 5).map((nation, index) => (
|
||||||
<Button
|
<Button
|
||||||
key={nation}
|
key={nation}
|
||||||
variant={
|
variant={filters.nations.includes(nation) ? 'solid' : 'soft'}
|
||||||
tankopediaState.filters.nations.includes(nation)
|
|
||||||
? 'solid'
|
|
||||||
: 'soft'
|
|
||||||
}
|
|
||||||
style={{
|
style={{
|
||||||
flex: 1,
|
flex: 1,
|
||||||
margin: -0.5,
|
margin: -0.5,
|
||||||
@@ -276,11 +258,7 @@ export function Options() {
|
|||||||
{nations.slice(5, 10).map((nation, index) => (
|
{nations.slice(5, 10).map((nation, index) => (
|
||||||
<Button
|
<Button
|
||||||
key={nation}
|
key={nation}
|
||||||
variant={
|
variant={filters.nations.includes(nation) ? 'solid' : 'soft'}
|
||||||
tankopediaState.filters.nations.includes(nation)
|
|
||||||
? 'solid'
|
|
||||||
: 'soft'
|
|
||||||
}
|
|
||||||
style={{
|
style={{
|
||||||
flex: 1,
|
flex: 1,
|
||||||
margin: -0.5,
|
margin: -0.5,
|
||||||
@@ -318,9 +296,7 @@ export function Options() {
|
|||||||
{[...TIERS].reverse().map((tier, index) => (
|
{[...TIERS].reverse().map((tier, index) => (
|
||||||
<Button
|
<Button
|
||||||
key={tier}
|
key={tier}
|
||||||
variant={
|
variant={filters.tiers.includes(tier) ? 'solid' : 'soft'}
|
||||||
tankopediaState.filters.tiers.includes(tier) ? 'solid' : 'soft'
|
|
||||||
}
|
|
||||||
style={{
|
style={{
|
||||||
flex: 1,
|
flex: 1,
|
||||||
margin: -0.5,
|
margin: -0.5,
|
||||||
@@ -351,11 +327,7 @@ export function Options() {
|
|||||||
{nations.map((nation, index) => (
|
{nations.map((nation, index) => (
|
||||||
<Button
|
<Button
|
||||||
key={nation}
|
key={nation}
|
||||||
variant={
|
variant={filters.nations.includes(nation) ? 'solid' : 'soft'}
|
||||||
tankopediaState.filters.nations.includes(nation)
|
|
||||||
? 'solid'
|
|
||||||
: 'soft'
|
|
||||||
}
|
|
||||||
style={{
|
style={{
|
||||||
flex: 1,
|
flex: 1,
|
||||||
margin: -0.5,
|
margin: -0.5,
|
||||||
|
|||||||
@@ -42,7 +42,8 @@ import * as styles from './page.css';
|
|||||||
const TANKS_PER_PAGE = 3 * 2 ** 3;
|
const TANKS_PER_PAGE = 3 * 2 ** 3;
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const tankopediaState = useTankopedia();
|
const filters = useTankopedia((state) => state.filters);
|
||||||
|
const sort = useTankopedia((state) => state.sort);
|
||||||
const awaitedTanks = use(tanksDefinitionsArray);
|
const awaitedTanks = use(tanksDefinitionsArray);
|
||||||
const input = useRef<HTMLInputElement>(null);
|
const input = useRef<HTMLInputElement>(null);
|
||||||
const searchableTanks = useMemo(
|
const searchableTanks = useMemo(
|
||||||
@@ -50,37 +51,37 @@ export default function Page() {
|
|||||||
awaitedTanks
|
awaitedTanks
|
||||||
.filter(
|
.filter(
|
||||||
(tank) =>
|
(tank) =>
|
||||||
(tankopediaState.filters.tiers.length === 0
|
(filters.tiers.length === 0
|
||||||
? true
|
? true
|
||||||
: tankopediaState.filters.tiers.includes(tank.tier)) &&
|
: filters.tiers.includes(tank.tier)) &&
|
||||||
(tankopediaState.filters.types.length === 0
|
(filters.types.length === 0
|
||||||
? true
|
? true
|
||||||
: tankopediaState.filters.types.includes(tank.type)) &&
|
: filters.types.includes(tank.type)) &&
|
||||||
(tankopediaState.filters.treeTypes.length === 0
|
(filters.treeTypes.length === 0
|
||||||
? true
|
? true
|
||||||
: tankopediaState.filters.treeTypes.includes(tank.tree_type)) &&
|
: filters.treeTypes.includes(tank.tree_type)) &&
|
||||||
(tankopediaState.filters.nations.length === 0
|
(filters.nations.length === 0
|
||||||
? true
|
? true
|
||||||
: tankopediaState.filters.nations.includes(tank.nation)) &&
|
: filters.nations.includes(tank.nation)) &&
|
||||||
(tankopediaState.filters.test === 'include'
|
(filters.test === 'include'
|
||||||
? true
|
? true
|
||||||
: tankopediaState.filters.test === 'exclude'
|
: filters.test === 'exclude'
|
||||||
? !tank.testing
|
? !tank.testing
|
||||||
: tank.testing),
|
: tank.testing),
|
||||||
)
|
)
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
let diff = 0;
|
let diff = 0;
|
||||||
|
|
||||||
if (tankopediaState.sort.by === 'tier') {
|
if (sort.by === 'tier') {
|
||||||
diff = a.tier - b.tier;
|
diff = a.tier - b.tier;
|
||||||
}
|
}
|
||||||
if (tankopediaState.sort.by === 'name') {
|
if (sort.by === 'name') {
|
||||||
diff = a.name.localeCompare(b.name);
|
diff = a.name.localeCompare(b.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
return tankopediaState.sort.direction === 'ascending' ? diff : -diff;
|
return sort.direction === 'ascending' ? diff : -diff;
|
||||||
}),
|
}),
|
||||||
[tankopediaState.filters, tankopediaState.sort],
|
[filters, sort],
|
||||||
);
|
);
|
||||||
const [searchedList, setSearchedList] = useState(searchableTanks);
|
const [searchedList, setSearchedList] = useState(searchableTanks);
|
||||||
const [page, setPage] = useState(0);
|
const [page, setPage] = useState(0);
|
||||||
@@ -136,7 +137,7 @@ export default function Page() {
|
|||||||
<DropdownMenu.Content>
|
<DropdownMenu.Content>
|
||||||
<DropdownMenu.Label>By</DropdownMenu.Label>
|
<DropdownMenu.Label>By</DropdownMenu.Label>
|
||||||
<DropdownMenu.RadioGroup
|
<DropdownMenu.RadioGroup
|
||||||
value={tankopediaState.sort.by}
|
value={sort.by}
|
||||||
onValueChange={(value) =>
|
onValueChange={(value) =>
|
||||||
mutateTankopedia((draft) => {
|
mutateTankopedia((draft) => {
|
||||||
draft.sort.by = value as TankopediaSortBy;
|
draft.sort.by = value as TankopediaSortBy;
|
||||||
@@ -155,7 +156,7 @@ export default function Page() {
|
|||||||
|
|
||||||
<DropdownMenu.Label>Direction</DropdownMenu.Label>
|
<DropdownMenu.Label>Direction</DropdownMenu.Label>
|
||||||
<DropdownMenu.RadioGroup
|
<DropdownMenu.RadioGroup
|
||||||
value={tankopediaState.sort.direction}
|
value={sort.direction}
|
||||||
onValueChange={(value) =>
|
onValueChange={(value) =>
|
||||||
mutateTankopedia((draft) => {
|
mutateTankopedia((draft) => {
|
||||||
draft.sort.direction = value as TankopediaSortDirection;
|
draft.sort.direction = value as TankopediaSortDirection;
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
// split shaders due to https://github.com/FarazzShaikh/THREE-CustomShaderMaterial/issues/48
|
|
||||||
|
|
||||||
varying vec3 vCSMViewPosition;
|
varying vec3 vCSMViewPosition;
|
||||||
uniform bool isExplosive;
|
uniform bool isExplosive;
|
||||||
uniform bool canSplash;
|
uniform bool canSplash;
|
||||||
|
|||||||
+20
-12
@@ -28,12 +28,16 @@ type Tankopedia = {
|
|||||||
test: TankopediaTestTankDisplay;
|
test: TankopediaTestTankDisplay;
|
||||||
};
|
};
|
||||||
model: {
|
model: {
|
||||||
hullYaw: number;
|
physical: {
|
||||||
turretYaw: number;
|
hullYaw: number;
|
||||||
gunPitch: number;
|
turretYaw: number;
|
||||||
controlsEnabled: boolean;
|
gunPitch: number;
|
||||||
showGrid: boolean;
|
};
|
||||||
showSpacedArmor: boolean;
|
visual: {
|
||||||
|
controlsEnabled: boolean;
|
||||||
|
showGrid: boolean;
|
||||||
|
showSpacedArmor: boolean;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
mode: TankopediaMode;
|
mode: TankopediaMode;
|
||||||
} & (
|
} & (
|
||||||
@@ -69,12 +73,16 @@ export const useTankopedia = create<Tankopedia>()(
|
|||||||
test: 'include',
|
test: 'include',
|
||||||
},
|
},
|
||||||
model: {
|
model: {
|
||||||
gunPitch: 0,
|
physical: {
|
||||||
hullYaw: 0,
|
gunPitch: 0,
|
||||||
turretYaw: 0,
|
hullYaw: 0,
|
||||||
controlsEnabled: true,
|
turretYaw: 0,
|
||||||
showGrid: true,
|
},
|
||||||
showSpacedArmor: true,
|
visual: {
|
||||||
|
controlsEnabled: true,
|
||||||
|
showGrid: true,
|
||||||
|
showSpacedArmor: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
areTanksAssigned: false,
|
areTanksAssigned: false,
|
||||||
mode: 'model',
|
mode: 'model',
|
||||||
|
|||||||
Reference in new issue
Block a user