mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-24 12:09:51 +02:00
feat: async updates
This commit is contained in:
1 parent
5e0e80e965
commit
237f676af8
9 files changed
+248
-304
No files matched your search
Vendored
+1
@@ -51,6 +51,7 @@
|
|||||||
"lods",
|
"lods",
|
||||||
"metallicness",
|
"metallicness",
|
||||||
"MIPMAPCOUNT",
|
"MIPMAPCOUNT",
|
||||||
|
"modeltransform",
|
||||||
"MURICA",
|
"MURICA",
|
||||||
"NEGATIVEX",
|
"NEGATIVEX",
|
||||||
"NEGATIVEY",
|
"NEGATIVEY",
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
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';
|
||||||
@@ -20,7 +19,6 @@ 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);
|
||||||
@@ -66,24 +64,16 @@ export function TankDisplay() {
|
|||||||
const gunModelDefinition = turretModelDefinition.guns[protagonist.gun.id];
|
const gunModelDefinition = turretModelDefinition.guns[protagonist.gun.id];
|
||||||
|
|
||||||
mutateTankopedia((draft) => {
|
mutateTankopedia((draft) => {
|
||||||
[draft.model.physical.gunPitch, draft.model.physical.turretYaw] =
|
[draft.model.physical.pitch, draft.model.physical.yaw] =
|
||||||
applyPitchYawLimits(
|
applyPitchYawLimits(
|
||||||
draft.model.physical.gunPitch,
|
draft.model.physical.pitch,
|
||||||
draft.model.physical.turretYaw,
|
draft.model.physical.yaw,
|
||||||
gunModelDefinition.pitch,
|
gunModelDefinition.pitch,
|
||||||
turretModelDefinition.yaw,
|
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 (
|
||||||
@@ -107,8 +97,6 @@ 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;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,14 +2,19 @@ import { GroupProps, useLoader } from '@react-three/fiber';
|
|||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { Euler, Group, Mesh, Vector3 } from 'three';
|
import { Euler, Group, Mesh, Vector3 } from 'three';
|
||||||
import { GLTFLoader } from 'three-stdlib';
|
import { GLTFLoader } from 'three-stdlib';
|
||||||
|
import { degToRad } from 'three/src/math/MathUtils';
|
||||||
import { ArmorMesh } from '../../../../../../../components/ArmorMesh';
|
import { ArmorMesh } from '../../../../../../../components/ArmorMesh';
|
||||||
import { HeadsUpDisplay } from '../../../../../../../components/HeadsUpDisplay';
|
import { HeadsUpDisplay } from '../../../../../../../components/HeadsUpDisplay';
|
||||||
import { X_AXIS } from '../../../../../../../constants/axis';
|
import { X_AXIS, Y_AXIS, Z_AXIS } from '../../../../../../../constants/axis';
|
||||||
import { asset } from '../../../../../../../core/blitzkrieg/asset';
|
import { asset } from '../../../../../../../core/blitzkrieg/asset';
|
||||||
import {
|
import {
|
||||||
ModelDefinitions,
|
ModelDefinitions,
|
||||||
modelDefinitions,
|
modelDefinitions,
|
||||||
} from '../../../../../../../core/blitzkrieg/modelDefinitions';
|
} from '../../../../../../../core/blitzkrieg/modelDefinitions';
|
||||||
|
import {
|
||||||
|
ModelTransformEventData,
|
||||||
|
modelTransformEvent,
|
||||||
|
} from '../../../../../../../core/blitzkrieg/modelTransform';
|
||||||
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';
|
||||||
@@ -17,7 +22,6 @@ import { useTankopedia } from '../../../../../../../stores/tankopedia';
|
|||||||
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
|
||||||
@@ -29,7 +33,9 @@ export function TankArmor({ ...props }: TankArmorProps) {
|
|||||||
const showSpacedArmor = useTankopedia(
|
const showSpacedArmor = useTankopedia(
|
||||||
(state) => state.model.visual.showSpacedArmor,
|
(state) => state.model.visual.showSpacedArmor,
|
||||||
);
|
);
|
||||||
const physical = useTankopedia((state) => state.model.physical);
|
const turretContainer = useRef<Group>(null);
|
||||||
|
const gunContainer = useRef<Group>(null);
|
||||||
|
const initialTankopediaState = useTankopedia.getState();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
@@ -37,8 +43,83 @@ export function TankArmor({ ...props }: TankArmorProps) {
|
|||||||
})();
|
})();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// it's ok to have hooks after this early termination because this will never be true
|
useEffect(() => {
|
||||||
// it's more for typescript to stop throwing a fit
|
if (!awaitedModelDefinitions) return;
|
||||||
|
|
||||||
|
const turretOrigin = new Vector3(
|
||||||
|
tankModelDefinition.turretOrigin[0],
|
||||||
|
tankModelDefinition.turretOrigin[1],
|
||||||
|
-tankModelDefinition.turretOrigin[2],
|
||||||
|
).applyAxisAngle(X_AXIS, Math.PI / 2);
|
||||||
|
const gunOrigin = new Vector3(
|
||||||
|
turretModelDefinition.gunOrigin[0],
|
||||||
|
turretModelDefinition.gunOrigin[1],
|
||||||
|
-turretModelDefinition.gunOrigin[2],
|
||||||
|
).applyAxisAngle(X_AXIS, Math.PI / 2);
|
||||||
|
const turretPosition = new Vector3();
|
||||||
|
const turretRotation = new Euler();
|
||||||
|
const gunPosition = new Vector3();
|
||||||
|
const gunRotation = new Euler();
|
||||||
|
|
||||||
|
function handleModelTransform({ yaw, pitch }: ModelTransformEventData) {
|
||||||
|
gunPosition
|
||||||
|
.set(0, 0, 0)
|
||||||
|
.sub(turretOrigin)
|
||||||
|
.sub(gunOrigin)
|
||||||
|
.applyAxisAngle(X_AXIS, pitch)
|
||||||
|
.add(gunOrigin)
|
||||||
|
.add(turretOrigin);
|
||||||
|
gunRotation.set(pitch, 0, 0);
|
||||||
|
gunContainer.current?.position.copy(gunPosition);
|
||||||
|
gunContainer.current?.rotation.copy(gunRotation);
|
||||||
|
|
||||||
|
if (yaw === undefined) return;
|
||||||
|
|
||||||
|
turretPosition
|
||||||
|
.set(0, 0, 0)
|
||||||
|
.sub(turretOrigin)
|
||||||
|
.applyAxisAngle(new Vector3(0, 0, 1), yaw);
|
||||||
|
turretRotation.set(0, 0, yaw);
|
||||||
|
|
||||||
|
if (tankModelDefinition.turretRotation) {
|
||||||
|
const initialPitch = -degToRad(
|
||||||
|
tankModelDefinition.turretRotation.pitch,
|
||||||
|
);
|
||||||
|
const initialYaw = -degToRad(tankModelDefinition.turretRotation.yaw);
|
||||||
|
const initialRoll = -degToRad(tankModelDefinition.turretRotation.roll);
|
||||||
|
|
||||||
|
turretPosition
|
||||||
|
.applyAxisAngle(X_AXIS, initialPitch)
|
||||||
|
.applyAxisAngle(Y_AXIS, initialRoll)
|
||||||
|
.applyAxisAngle(Z_AXIS, initialYaw);
|
||||||
|
turretRotation.x += initialPitch;
|
||||||
|
turretRotation.y += initialRoll;
|
||||||
|
turretRotation.z += initialYaw;
|
||||||
|
}
|
||||||
|
|
||||||
|
turretPosition.add(turretOrigin);
|
||||||
|
turretContainer.current?.position.copy(turretPosition);
|
||||||
|
turretContainer.current?.rotation.copy(turretRotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
modelTransformEvent.on(handleModelTransform);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
modelTransformEvent.off(handleModelTransform);
|
||||||
|
};
|
||||||
|
}, [awaitedModelDefinitions]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const unsubscribe = useTankopedia.subscribe(
|
||||||
|
(state) => state.mode,
|
||||||
|
(mode) => {
|
||||||
|
if (wrapper.current) wrapper.current.visible = mode === 'armor';
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return unsubscribe;
|
||||||
|
});
|
||||||
|
|
||||||
if (!protagonist) return null;
|
if (!protagonist) return null;
|
||||||
|
|
||||||
const armorGltf = useLoader(
|
const armorGltf = useLoader(
|
||||||
@@ -68,34 +149,14 @@ export function TankArmor({ ...props }: TankArmorProps) {
|
|||||||
turretModelDefinition.gunOrigin[1],
|
turretModelDefinition.gunOrigin[1],
|
||||||
-turretModelDefinition.gunOrigin[2],
|
-turretModelDefinition.gunOrigin[2],
|
||||||
).applyAxisAngle(X_AXIS, Math.PI / 2);
|
).applyAxisAngle(X_AXIS, Math.PI / 2);
|
||||||
const turretPosition = new Vector3()
|
|
||||||
.sub(turretOrigin)
|
|
||||||
.applyAxisAngle(new Vector3(0, 0, 1), physical.turretYaw);
|
|
||||||
const turretRotation = new Euler(0, 0, physical.turretYaw);
|
|
||||||
|
|
||||||
if (tankModelDefinition.turretRotation) {
|
|
||||||
const pitch = -tankModelDefinition.turretRotation.pitch * (Math.PI / 180);
|
|
||||||
const yaw = -tankModelDefinition.turretRotation.yaw * (Math.PI / 180);
|
|
||||||
const roll = -tankModelDefinition.turretRotation.roll * (Math.PI / 180);
|
|
||||||
|
|
||||||
turretPosition
|
|
||||||
.applyAxisAngle(new Vector3(1, 0, 0), pitch)
|
|
||||||
.applyAxisAngle(new Vector3(0, 1, 0), roll)
|
|
||||||
.applyAxisAngle(new Vector3(0, 0, 1), yaw);
|
|
||||||
turretRotation.x += pitch;
|
|
||||||
turretRotation.y += roll;
|
|
||||||
turretRotation.z += yaw;
|
|
||||||
}
|
|
||||||
|
|
||||||
turretPosition.add(turretOrigin);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HeadsUpDisplay>
|
<HeadsUpDisplay>
|
||||||
<group
|
<group
|
||||||
{...props}
|
{...props}
|
||||||
visible={mode === 'armor'}
|
|
||||||
ref={wrapper}
|
ref={wrapper}
|
||||||
rotation={[-Math.PI / 2, 0, physical.hullYaw]}
|
rotation={[-Math.PI / 2, 0, 0]}
|
||||||
|
visible={initialTankopediaState.mode === 'armor'}
|
||||||
>
|
>
|
||||||
{armorNodes.map((node) => {
|
{armorNodes.map((node) => {
|
||||||
const isHull = node.name.startsWith('hull_');
|
const isHull = node.name.startsWith('hull_');
|
||||||
@@ -142,7 +203,7 @@ export function TankArmor({ ...props }: TankArmorProps) {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
<group position={turretPosition} rotation={turretRotation}>
|
<group ref={turretContainer}>
|
||||||
{armorNodes.map((node) => {
|
{armorNodes.map((node) => {
|
||||||
const isCurrentTurret = node.name.startsWith(
|
const isCurrentTurret = node.name.startsWith(
|
||||||
`turret_${turretModelDefinition.model
|
`turret_${turretModelDefinition.model
|
||||||
@@ -173,15 +234,7 @@ export function TankArmor({ ...props }: TankArmorProps) {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
<group
|
<group ref={gunContainer}>
|
||||||
position={new Vector3()
|
|
||||||
.sub(turretOrigin)
|
|
||||||
.sub(gunOrigin)
|
|
||||||
.applyAxisAngle(new Vector3(1, 0, 0), physical.gunPitch)
|
|
||||||
.add(turretOrigin)
|
|
||||||
.add(gunOrigin)}
|
|
||||||
rotation={[physical.gunPitch, 0, 0]}
|
|
||||||
>
|
|
||||||
{armorNodes.map((node) => {
|
{armorNodes.map((node) => {
|
||||||
const isCurrentGun = node.name.startsWith(
|
const isCurrentGun = node.name.startsWith(
|
||||||
`gun_${gunModelDefinition.model.toString().padStart(2, '0')}`,
|
`gun_${gunModelDefinition.model.toString().padStart(2, '0')}`,
|
||||||
|
|||||||
@@ -9,13 +9,18 @@ import {
|
|||||||
Vector3,
|
Vector3,
|
||||||
} from 'three';
|
} from 'three';
|
||||||
import { GLTFLoader } from 'three-stdlib';
|
import { GLTFLoader } from 'three-stdlib';
|
||||||
import { X_AXIS } from '../../../../../../../constants/axis';
|
import { degToRad } from 'three/src/math/MathUtils';
|
||||||
|
import { X_AXIS, Y_AXIS, Z_AXIS } from '../../../../../../../constants/axis';
|
||||||
import { applyPitchYawLimits } from '../../../../../../../core/blitz/applyPitchYawLimits';
|
import { applyPitchYawLimits } from '../../../../../../../core/blitz/applyPitchYawLimits';
|
||||||
import { asset } from '../../../../../../../core/blitzkrieg/asset';
|
import { asset } from '../../../../../../../core/blitzkrieg/asset';
|
||||||
import {
|
import {
|
||||||
ModelDefinitions,
|
ModelDefinitions,
|
||||||
modelDefinitions,
|
modelDefinitions,
|
||||||
} from '../../../../../../../core/blitzkrieg/modelDefinitions';
|
} from '../../../../../../../core/blitzkrieg/modelDefinitions';
|
||||||
|
import {
|
||||||
|
ModelTransformEventData,
|
||||||
|
modelTransformEvent,
|
||||||
|
} from '../../../../../../../core/blitzkrieg/modelTransform';
|
||||||
import { resolveJsxTree } from '../../../../../../../core/blitzkrieg/resolveJsxTree';
|
import { resolveJsxTree } from '../../../../../../../core/blitzkrieg/resolveJsxTree';
|
||||||
import { normalizeAnglePI } from '../../../../../../../core/math/normalizeAngle180';
|
import { normalizeAnglePI } from '../../../../../../../core/math/normalizeAngle180';
|
||||||
import mutateTankopedia, {
|
import mutateTankopedia, {
|
||||||
@@ -28,11 +33,14 @@ export function TankModel() {
|
|||||||
const [awaitedModelDefinitions, setAwaitedModelDefinitions] = useState<
|
const [awaitedModelDefinitions, setAwaitedModelDefinitions] = useState<
|
||||||
ModelDefinitions | undefined
|
ModelDefinitions | undefined
|
||||||
>(undefined);
|
>(undefined);
|
||||||
const canvas = useThree((state) => state.gl.domElement);
|
|
||||||
const protagonist = useTankopedia((state) => {
|
const protagonist = useTankopedia((state) => {
|
||||||
if (!state.areTanksAssigned) return;
|
if (!state.areTanksAssigned) return;
|
||||||
return state.protagonist;
|
return state.protagonist;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!protagonist) return null;
|
||||||
|
|
||||||
|
const canvas = useThree((state) => state.gl.domElement);
|
||||||
const physical = useTankopedia((state) => state.model.physical);
|
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);
|
||||||
@@ -44,7 +52,78 @@ export function TankModel() {
|
|||||||
})();
|
})();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (!protagonist) return null;
|
useEffect(() => {
|
||||||
|
if (!awaitedModelDefinitions) return;
|
||||||
|
|
||||||
|
const turretOrigin = new Vector3(
|
||||||
|
tankModelDefinition.turretOrigin[0],
|
||||||
|
tankModelDefinition.turretOrigin[1],
|
||||||
|
-tankModelDefinition.turretOrigin[2],
|
||||||
|
).applyAxisAngle(X_AXIS, Math.PI / 2);
|
||||||
|
const gunOrigin = new Vector3(
|
||||||
|
turretModelDefinition.gunOrigin[0],
|
||||||
|
turretModelDefinition.gunOrigin[1],
|
||||||
|
-turretModelDefinition.gunOrigin[2],
|
||||||
|
).applyAxisAngle(X_AXIS, Math.PI / 2);
|
||||||
|
const turretPosition = new Vector3();
|
||||||
|
const turretRotation = new Euler();
|
||||||
|
const gunPosition = new Vector3();
|
||||||
|
const gunRotation = new Euler();
|
||||||
|
|
||||||
|
function handleModelTransform({ yaw, pitch }: ModelTransformEventData) {
|
||||||
|
gunPosition
|
||||||
|
.set(0, 0, 0)
|
||||||
|
.sub(turretOrigin)
|
||||||
|
.sub(gunOrigin)
|
||||||
|
.applyAxisAngle(X_AXIS, pitch)
|
||||||
|
.add(gunOrigin)
|
||||||
|
.add(turretOrigin);
|
||||||
|
gunRotation.set(pitch, 0, 0);
|
||||||
|
gunContainer.current?.position.copy(gunPosition);
|
||||||
|
gunContainer.current?.rotation.copy(gunRotation);
|
||||||
|
|
||||||
|
if (yaw === undefined) return;
|
||||||
|
|
||||||
|
turretPosition
|
||||||
|
.set(0, 0, 0)
|
||||||
|
.sub(turretOrigin)
|
||||||
|
.applyAxisAngle(new Vector3(0, 0, 1), yaw);
|
||||||
|
turretRotation.set(0, 0, yaw);
|
||||||
|
|
||||||
|
if (tankModelDefinition.turretRotation) {
|
||||||
|
const initialPitch = -degToRad(
|
||||||
|
tankModelDefinition.turretRotation.pitch,
|
||||||
|
);
|
||||||
|
const initialYaw = -degToRad(tankModelDefinition.turretRotation.yaw);
|
||||||
|
const initialRoll = -degToRad(tankModelDefinition.turretRotation.roll);
|
||||||
|
|
||||||
|
turretPosition
|
||||||
|
.applyAxisAngle(X_AXIS, initialPitch)
|
||||||
|
.applyAxisAngle(Y_AXIS, initialRoll)
|
||||||
|
.applyAxisAngle(Z_AXIS, initialYaw);
|
||||||
|
turretRotation.x += initialPitch;
|
||||||
|
turretRotation.y += initialRoll;
|
||||||
|
turretRotation.z += initialYaw;
|
||||||
|
}
|
||||||
|
|
||||||
|
turretPosition.add(turretOrigin);
|
||||||
|
turretContainer.current?.position.copy(turretPosition);
|
||||||
|
turretContainer.current?.rotation.copy(turretRotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
modelTransformEvent.on(handleModelTransform);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
modelTransformEvent.off(handleModelTransform);
|
||||||
|
};
|
||||||
|
}, [awaitedModelDefinitions]);
|
||||||
|
|
||||||
|
if (!awaitedModelDefinitions) return;
|
||||||
|
|
||||||
|
const tankModelDefinition = awaitedModelDefinitions[protagonist.tank.id];
|
||||||
|
const turretModelDefinition =
|
||||||
|
tankModelDefinition.turrets[protagonist.turret.id];
|
||||||
|
const gunModelDefinition = turretModelDefinition.guns[protagonist.gun.id];
|
||||||
|
|
||||||
const gltf = useLoader(
|
const gltf = useLoader(
|
||||||
GLTFLoader,
|
GLTFLoader,
|
||||||
@@ -52,51 +131,13 @@ export function TankModel() {
|
|||||||
);
|
);
|
||||||
const nodes = Object.values(gltf.nodes);
|
const nodes = Object.values(gltf.nodes);
|
||||||
|
|
||||||
if (!awaitedModelDefinitions) return null;
|
|
||||||
|
|
||||||
const tankModelDefinition = awaitedModelDefinitions[protagonist.tank.id];
|
|
||||||
const turretModelDefinition =
|
|
||||||
tankModelDefinition.turrets[protagonist.turret.id];
|
|
||||||
const gunModelDefinition = turretModelDefinition.guns[protagonist.gun.id];
|
|
||||||
const turretOrigin = new Vector3(
|
|
||||||
tankModelDefinition.turretOrigin[0],
|
|
||||||
tankModelDefinition.turretOrigin[1],
|
|
||||||
-tankModelDefinition.turretOrigin[2],
|
|
||||||
).applyAxisAngle(X_AXIS, Math.PI / 2);
|
|
||||||
const gunOrigin = new Vector3(
|
|
||||||
turretModelDefinition.gunOrigin[0],
|
|
||||||
turretModelDefinition.gunOrigin[1],
|
|
||||||
-turretModelDefinition.gunOrigin[2],
|
|
||||||
).applyAxisAngle(X_AXIS, Math.PI / 2);
|
|
||||||
const turretPosition = new Vector3()
|
|
||||||
.sub(turretOrigin)
|
|
||||||
.applyAxisAngle(new Vector3(0, 0, 1), physical.turretYaw);
|
|
||||||
const turretRotation = new Euler(0, 0, physical.turretYaw);
|
|
||||||
|
|
||||||
if (tankModelDefinition.turretRotation) {
|
|
||||||
const pitch = -tankModelDefinition.turretRotation.pitch * (Math.PI / 180);
|
|
||||||
const yaw = -tankModelDefinition.turretRotation.yaw * (Math.PI / 180);
|
|
||||||
const roll = -tankModelDefinition.turretRotation.roll * (Math.PI / 180);
|
|
||||||
|
|
||||||
turretPosition
|
|
||||||
.applyAxisAngle(new Vector3(1, 0, 0), pitch)
|
|
||||||
.applyAxisAngle(new Vector3(0, 1, 0), roll)
|
|
||||||
.applyAxisAngle(new Vector3(0, 0, 1), yaw);
|
|
||||||
turretRotation.x += pitch;
|
|
||||||
turretRotation.y += roll;
|
|
||||||
turretRotation.z += yaw;
|
|
||||||
}
|
|
||||||
|
|
||||||
turretPosition.add(turretOrigin);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<group ref={hullContainer} rotation={[-Math.PI / 2, 0, physical.hullYaw]}>
|
<group ref={hullContainer} rotation={[-Math.PI / 2, 0, 0]}>
|
||||||
{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_');
|
||||||
const isTrack = node.name.startsWith('chassis_track_');
|
const isTrack = node.name.startsWith('chassis_track_');
|
||||||
const isVisible = isHull || isWheel || isTrack;
|
const isVisible = isHull || isWheel || isTrack;
|
||||||
let draftHullYaw = 0;
|
|
||||||
|
|
||||||
function handlePointerDown(event: ThreeEvent<PointerEvent>) {
|
function handlePointerDown(event: ThreeEvent<PointerEvent>) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
@@ -104,31 +145,12 @@ export function TankModel() {
|
|||||||
draft.model.visual.controlsEnabled = false;
|
draft.model.visual.controlsEnabled = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isHull) {
|
if (isTrack) {
|
||||||
draftHullYaw = physical.hullYaw;
|
window.addEventListener('pointermove', handlePointerMove);
|
||||||
|
window.addEventListener('pointerup', handlePointerUp);
|
||||||
window.addEventListener('pointermove', handlePointerMoveHull);
|
|
||||||
window.addEventListener('pointerup', handlePointerUpHull);
|
|
||||||
} else if (isTrack) {
|
|
||||||
window.addEventListener('pointermove', handlePointerMoveTrack);
|
|
||||||
window.addEventListener('pointerup', handlePointerUpTrack);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function handlePointerMoveHull(event: PointerEvent) {
|
function handlePointerMove(event: PointerEvent) {
|
||||||
draftHullYaw += event.movementX * ((2 * Math.PI) / canvas.width);
|
|
||||||
if (hullContainer.current) {
|
|
||||||
hullContainer.current.rotation.z = draftHullYaw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function handlePointerUpHull() {
|
|
||||||
mutateTankopedia((draft) => {
|
|
||||||
draft.model.visual.controlsEnabled = true;
|
|
||||||
draft.model.physical.hullYaw = normalizeAnglePI(draftHullYaw);
|
|
||||||
});
|
|
||||||
window.removeEventListener('pointermove', handlePointerMoveHull);
|
|
||||||
window.removeEventListener('pointerup', handlePointerUpHull);
|
|
||||||
}
|
|
||||||
function handlePointerMoveTrack(event: PointerEvent) {
|
|
||||||
const mesh = node as Mesh;
|
const mesh = node as Mesh;
|
||||||
const material = mesh.material as MeshStandardMaterial;
|
const material = mesh.material as MeshStandardMaterial;
|
||||||
const offset = new Vector2(
|
const offset = new Vector2(
|
||||||
@@ -141,12 +163,12 @@ export function TankModel() {
|
|||||||
material.roughnessMap?.offset.add(offset);
|
material.roughnessMap?.offset.add(offset);
|
||||||
material.normalMap?.offset.add(offset);
|
material.normalMap?.offset.add(offset);
|
||||||
}
|
}
|
||||||
function handlePointerUpTrack() {
|
function handlePointerUp() {
|
||||||
mutateTankopedia((draft) => {
|
mutateTankopedia((draft) => {
|
||||||
draft.model.visual.controlsEnabled = true;
|
draft.model.visual.controlsEnabled = true;
|
||||||
});
|
});
|
||||||
window.removeEventListener('pointermove', handlePointerMoveTrack);
|
window.removeEventListener('pointermove', handlePointerMove);
|
||||||
window.removeEventListener('pointerup', handlePointerUpTrack);
|
window.removeEventListener('pointerup', handlePointerUp);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isVisible) return null;
|
if (!isVisible) return null;
|
||||||
@@ -167,26 +189,22 @@ export function TankModel() {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
<group
|
<group ref={turretContainer}>
|
||||||
position={turretPosition}
|
|
||||||
rotation={turretRotation}
|
|
||||||
ref={turretContainer}
|
|
||||||
>
|
|
||||||
{nodes.map((node) => {
|
{nodes.map((node) => {
|
||||||
const isTurret = node.name.startsWith('turret_');
|
const isTurret = node.name.startsWith('turret_');
|
||||||
const isCurrentTurret =
|
const isCurrentTurret =
|
||||||
node.name ===
|
node.name ===
|
||||||
`turret_${turretModelDefinition.model.toString().padStart(2, '0')}`;
|
`turret_${turretModelDefinition.model.toString().padStart(2, '0')}`;
|
||||||
const isVisible = isCurrentTurret;
|
const isVisible = isCurrentTurret;
|
||||||
let draftPitch = 0;
|
let pitch = 0;
|
||||||
let draftYaw = 0;
|
let yaw = 0;
|
||||||
|
|
||||||
function handlePointerDown(event: ThreeEvent<PointerEvent>) {
|
function handlePointerDown(event: ThreeEvent<PointerEvent>) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
if (isTurret) {
|
if (isTurret) {
|
||||||
draftYaw = physical.turretYaw;
|
yaw = physical.yaw;
|
||||||
draftPitch = physical.gunPitch;
|
pitch = physical.pitch;
|
||||||
|
|
||||||
mutateTankopedia((draft) => {
|
mutateTankopedia((draft) => {
|
||||||
draft.model.visual.controlsEnabled = false;
|
draft.model.visual.controlsEnabled = false;
|
||||||
@@ -196,53 +214,19 @@ export function TankModel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
function handlePointerMove(event: PointerEvent) {
|
function handlePointerMove(event: PointerEvent) {
|
||||||
[draftPitch, draftYaw] = applyPitchYawLimits(
|
[pitch, yaw] = applyPitchYawLimits(
|
||||||
draftPitch,
|
pitch,
|
||||||
draftYaw + event.movementX * (Math.PI / canvas.width),
|
yaw + event.movementX * (Math.PI / canvas.width),
|
||||||
gunModelDefinition.pitch,
|
gunModelDefinition.pitch,
|
||||||
turretModelDefinition.yaw,
|
turretModelDefinition.yaw,
|
||||||
);
|
);
|
||||||
|
modelTransformEvent.emit({ pitch, yaw });
|
||||||
if (gunContainer.current) {
|
|
||||||
gunContainer.current.position
|
|
||||||
.set(0, 0, 0)
|
|
||||||
.sub(turretOrigin)
|
|
||||||
.sub(gunOrigin)
|
|
||||||
.applyAxisAngle(new Vector3(1, 0, 0), draftPitch)
|
|
||||||
.add(turretOrigin)
|
|
||||||
.add(gunOrigin);
|
|
||||||
gunContainer.current.rotation.x = draftPitch;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (turretContainer.current) {
|
|
||||||
turretContainer.current.position
|
|
||||||
.set(0, 0, 0)
|
|
||||||
.sub(turretOrigin)
|
|
||||||
.applyAxisAngle(new Vector3(0, 0, 1), draftYaw);
|
|
||||||
turretContainer.current.rotation.z = draftYaw;
|
|
||||||
|
|
||||||
if (tankModelDefinition.turretRotation) {
|
|
||||||
const pitch =
|
|
||||||
-tankModelDefinition.turretRotation.pitch * (Math.PI / 180);
|
|
||||||
const yaw =
|
|
||||||
-tankModelDefinition.turretRotation.yaw * (Math.PI / 180);
|
|
||||||
const roll =
|
|
||||||
-tankModelDefinition.turretRotation.roll * (Math.PI / 180);
|
|
||||||
|
|
||||||
turretContainer.current.position
|
|
||||||
.applyAxisAngle(new Vector3(1, 0, 0), pitch)
|
|
||||||
.applyAxisAngle(new Vector3(0, 1, 0), roll)
|
|
||||||
.applyAxisAngle(new Vector3(0, 0, 1), yaw);
|
|
||||||
}
|
|
||||||
|
|
||||||
turretContainer.current.position.add(turretOrigin);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
function handlePointerUp() {
|
function handlePointerUp() {
|
||||||
mutateTankopedia((state) => {
|
mutateTankopedia((state) => {
|
||||||
state.model.visual.controlsEnabled = true;
|
state.model.visual.controlsEnabled = true;
|
||||||
state.model.physical.gunPitch = normalizeAnglePI(draftPitch);
|
state.model.physical.pitch = normalizeAnglePI(pitch);
|
||||||
state.model.physical.turretYaw = normalizeAnglePI(draftYaw);
|
state.model.physical.yaw = normalizeAnglePI(yaw);
|
||||||
});
|
});
|
||||||
window.removeEventListener('pointermove', handlePointerMove);
|
window.removeEventListener('pointermove', handlePointerMove);
|
||||||
window.removeEventListener('pointerup', handlePointerUp);
|
window.removeEventListener('pointerup', handlePointerUp);
|
||||||
@@ -267,16 +251,7 @@ export function TankModel() {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
<group
|
<group ref={gunContainer}>
|
||||||
position={new Vector3()
|
|
||||||
.sub(turretOrigin)
|
|
||||||
.sub(gunOrigin)
|
|
||||||
.applyAxisAngle(new Vector3(1, 0, 0), physical.gunPitch)
|
|
||||||
.add(turretOrigin)
|
|
||||||
.add(gunOrigin)}
|
|
||||||
rotation={[physical.gunPitch, 0, 0]}
|
|
||||||
ref={gunContainer}
|
|
||||||
>
|
|
||||||
{nodes.map((node) => {
|
{nodes.map((node) => {
|
||||||
const isCurrentMantlet =
|
const isCurrentMantlet =
|
||||||
node.name ===
|
node.name ===
|
||||||
@@ -287,8 +262,8 @@ export function TankModel() {
|
|||||||
node.name ===
|
node.name ===
|
||||||
`gun_${gunModelDefinition.model.toString().padStart(2, '0')}`;
|
`gun_${gunModelDefinition.model.toString().padStart(2, '0')}`;
|
||||||
const isVisible = isCurrentGun || isCurrentMantlet;
|
const isVisible = isCurrentGun || isCurrentMantlet;
|
||||||
let draftPitch = 0;
|
let pitch = 0;
|
||||||
let draftYaw = 0;
|
let yaw = 0;
|
||||||
|
|
||||||
function handlePointerDown(event: ThreeEvent<PointerEvent>) {
|
function handlePointerDown(event: ThreeEvent<PointerEvent>) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
@@ -296,59 +271,25 @@ export function TankModel() {
|
|||||||
mutateTankopedia((draft) => {
|
mutateTankopedia((draft) => {
|
||||||
draft.model.visual.controlsEnabled = false;
|
draft.model.visual.controlsEnabled = false;
|
||||||
});
|
});
|
||||||
draftPitch = physical.gunPitch;
|
pitch = physical.pitch;
|
||||||
draftYaw = physical.turretYaw;
|
yaw = physical.yaw;
|
||||||
window.addEventListener('pointermove', handlePointerMove);
|
window.addEventListener('pointermove', handlePointerMove);
|
||||||
window.addEventListener('pointerup', handlePointerUp);
|
window.addEventListener('pointerup', handlePointerUp);
|
||||||
}
|
}
|
||||||
function handlePointerMove(event: PointerEvent) {
|
function handlePointerMove(event: PointerEvent) {
|
||||||
[draftPitch, draftYaw] = applyPitchYawLimits(
|
[pitch, yaw] = applyPitchYawLimits(
|
||||||
draftPitch - event.movementY * (Math.PI / canvas.height),
|
pitch - event.movementY * (Math.PI / canvas.height),
|
||||||
draftYaw + event.movementX * (Math.PI / canvas.width),
|
yaw + event.movementX * (Math.PI / canvas.width),
|
||||||
gunModelDefinition.pitch,
|
gunModelDefinition.pitch,
|
||||||
turretModelDefinition.yaw,
|
turretModelDefinition.yaw,
|
||||||
);
|
);
|
||||||
|
modelTransformEvent.emit({ pitch, yaw });
|
||||||
if (gunContainer.current) {
|
|
||||||
gunContainer.current.position
|
|
||||||
.set(0, 0, 0)
|
|
||||||
.sub(turretOrigin)
|
|
||||||
.sub(gunOrigin)
|
|
||||||
.applyAxisAngle(new Vector3(1, 0, 0), draftPitch)
|
|
||||||
.add(turretOrigin)
|
|
||||||
.add(gunOrigin);
|
|
||||||
gunContainer.current.rotation.x = draftPitch;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (turretContainer.current) {
|
|
||||||
turretContainer.current.position
|
|
||||||
.set(0, 0, 0)
|
|
||||||
.sub(turretOrigin)
|
|
||||||
.applyAxisAngle(new Vector3(0, 0, 1), draftYaw);
|
|
||||||
turretContainer.current.rotation.z = draftYaw;
|
|
||||||
|
|
||||||
if (tankModelDefinition.turretRotation) {
|
|
||||||
const pitch =
|
|
||||||
-tankModelDefinition.turretRotation.pitch * (Math.PI / 180);
|
|
||||||
const yaw =
|
|
||||||
-tankModelDefinition.turretRotation.yaw * (Math.PI / 180);
|
|
||||||
const roll =
|
|
||||||
-tankModelDefinition.turretRotation.roll * (Math.PI / 180);
|
|
||||||
|
|
||||||
turretContainer.current.position
|
|
||||||
.applyAxisAngle(new Vector3(1, 0, 0), pitch)
|
|
||||||
.applyAxisAngle(new Vector3(0, 1, 0), roll)
|
|
||||||
.applyAxisAngle(new Vector3(0, 0, 1), yaw);
|
|
||||||
}
|
|
||||||
|
|
||||||
turretContainer.current.position.add(turretOrigin);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
function handlePointerUp() {
|
function handlePointerUp() {
|
||||||
mutateTankopedia((state) => {
|
mutateTankopedia((state) => {
|
||||||
state.model.visual.controlsEnabled = true;
|
state.model.visual.controlsEnabled = true;
|
||||||
state.model.physical.gunPitch = normalizeAnglePI(draftPitch);
|
state.model.physical.pitch = normalizeAnglePI(pitch);
|
||||||
state.model.physical.turretYaw = normalizeAnglePI(draftYaw);
|
state.model.physical.yaw = normalizeAnglePI(yaw);
|
||||||
});
|
});
|
||||||
window.removeEventListener('pointermove', handlePointerMove);
|
window.removeEventListener('pointermove', handlePointerMove);
|
||||||
window.removeEventListener('pointerup', handlePointerUp);
|
window.removeEventListener('pointerup', handlePointerUp);
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { Flex, TextField } from '@radix-ui/themes';
|
|||||||
import { use, useEffect, useRef } from 'react';
|
import { use, useEffect, useRef } 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 { normalizeAnglePI } from '../../../../../core/math/normalizeAngle180';
|
|
||||||
import mutateTankopedia, {
|
import mutateTankopedia, {
|
||||||
useTankopedia,
|
useTankopedia,
|
||||||
} from '../../../../../stores/tankopedia';
|
} from '../../../../../stores/tankopedia';
|
||||||
@@ -11,28 +10,22 @@ export function RotationInputs() {
|
|||||||
const physical = useTankopedia((state) => state.model.physical);
|
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 gunPitchInput = useRef<HTMLInputElement>(null);
|
const gunPitchInput = useRef<HTMLInputElement>(null);
|
||||||
const protagonist = useTankopedia((state) => {
|
const protagonist = useTankopedia((state) => {
|
||||||
if (!state.areTanksAssigned) return;
|
if (!state.areTanksAssigned) return;
|
||||||
return state.protagonist;
|
return state.protagonist;
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
hullYawInput.current!.value = `${-Math.round(
|
|
||||||
physical.hullYaw * (180 / Math.PI),
|
|
||||||
)}`;
|
|
||||||
}, [physical.hullYaw]);
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
turretYawInput.current!.value = `${-Math.round(
|
turretYawInput.current!.value = `${-Math.round(
|
||||||
physical.turretYaw * (180 / Math.PI),
|
physical.yaw * (180 / Math.PI),
|
||||||
)}`;
|
)}`;
|
||||||
}, [physical.turretYaw]);
|
}, [physical.yaw]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
gunPitchInput.current!.value = `${-Math.round(
|
gunPitchInput.current!.value = `${-Math.round(
|
||||||
physical.gunPitch * (180 / Math.PI),
|
physical.pitch * (180 / Math.PI),
|
||||||
)}`;
|
)}`;
|
||||||
}, [physical.gunPitch]);
|
}, [physical.pitch]);
|
||||||
|
|
||||||
if (!protagonist) return null;
|
if (!protagonist) return null;
|
||||||
|
|
||||||
@@ -55,38 +48,9 @@ export function RotationInputs() {
|
|||||||
<Flex
|
<Flex
|
||||||
gap="2"
|
gap="2"
|
||||||
style={{
|
style={{
|
||||||
width: 352,
|
width: 256,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TextField.Root
|
|
||||||
style={{
|
|
||||||
flex: 1,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<TextField.Slot>Hull</TextField.Slot>
|
|
||||||
<TextField.Input
|
|
||||||
defaultValue={Math.round(physical.hullYaw * (180 / Math.PI))}
|
|
||||||
onBlur={() => {
|
|
||||||
const normalized = normalizeAnglePI(
|
|
||||||
-Number(hullYawInput.current?.value) * (Math.PI / 180),
|
|
||||||
);
|
|
||||||
mutateTankopedia((state) => {
|
|
||||||
state.model.physical.hullYaw = normalized;
|
|
||||||
});
|
|
||||||
hullYawInput.current!.value = `${Math.round(normalized)}`;
|
|
||||||
}}
|
|
||||||
onKeyDown={(event) => {
|
|
||||||
if (event.key === 'Enter') {
|
|
||||||
hullYawInput.current?.blur();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onFocus={() => hullYawInput.current?.focus()}
|
|
||||||
ref={hullYawInput}
|
|
||||||
style={{ textAlign: 'right' }}
|
|
||||||
/>
|
|
||||||
<TextField.Slot>°</TextField.Slot>
|
|
||||||
</TextField.Root>
|
|
||||||
|
|
||||||
<TextField.Root
|
<TextField.Root
|
||||||
style={{
|
style={{
|
||||||
flex: 1,
|
flex: 1,
|
||||||
@@ -94,17 +58,17 @@ export function RotationInputs() {
|
|||||||
>
|
>
|
||||||
<TextField.Slot>Turret</TextField.Slot>
|
<TextField.Slot>Turret</TextField.Slot>
|
||||||
<TextField.Input
|
<TextField.Input
|
||||||
defaultValue={Math.round(physical.turretYaw * (180 / Math.PI))}
|
defaultValue={Math.round(physical.yaw * (180 / Math.PI))}
|
||||||
onBlur={() => {
|
onBlur={() => {
|
||||||
const [pitch, yaw] = applyPitchYawLimits(
|
const [pitch, yaw] = applyPitchYawLimits(
|
||||||
physical.gunPitch,
|
physical.pitch,
|
||||||
-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.physical.gunPitch = pitch;
|
state.model.physical.pitch = pitch;
|
||||||
state.model.physical.turretYaw = yaw;
|
state.model.physical.yaw = yaw;
|
||||||
});
|
});
|
||||||
turretYawInput.current!.value = `${Math.round(
|
turretYawInput.current!.value = `${Math.round(
|
||||||
yaw * (180 / Math.PI),
|
yaw * (180 / Math.PI),
|
||||||
@@ -129,17 +93,17 @@ export function RotationInputs() {
|
|||||||
>
|
>
|
||||||
<TextField.Slot>Gun</TextField.Slot>
|
<TextField.Slot>Gun</TextField.Slot>
|
||||||
<TextField.Input
|
<TextField.Input
|
||||||
defaultValue={-Math.round(physical.gunPitch * (180 / Math.PI))}
|
defaultValue={-Math.round(physical.pitch * (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),
|
||||||
physical.turretYaw,
|
physical.yaw,
|
||||||
gunModelDefinition.pitch,
|
gunModelDefinition.pitch,
|
||||||
turretModelDefinition.yaw,
|
turretModelDefinition.yaw,
|
||||||
);
|
);
|
||||||
mutateTankopedia((state) => {
|
mutateTankopedia((state) => {
|
||||||
state.model.physical.gunPitch = pitch;
|
state.model.physical.pitch = pitch;
|
||||||
state.model.physical.turretYaw = yaw;
|
state.model.physical.yaw = yaw;
|
||||||
});
|
});
|
||||||
gunPitchInput.current!.value = `${Math.round(
|
gunPitchInput.current!.value = `${Math.round(
|
||||||
pitch * (180 / Math.PI),
|
pitch * (180 / Math.PI),
|
||||||
|
|||||||
@@ -18,9 +18,8 @@ export default function Page({ params }: { params: { id: string } }) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
mutateTankopedia((draft) => {
|
mutateTankopedia((draft) => {
|
||||||
draft.model.physical.hullYaw = 0;
|
draft.model.physical.yaw = 0;
|
||||||
draft.model.physical.turretYaw = 0;
|
draft.model.physical.pitch = 0;
|
||||||
draft.model.physical.gunPitch = 0;
|
|
||||||
draft.mode = 'model';
|
draft.mode = 'model';
|
||||||
draft.areTanksAssigned = true;
|
draft.areTanksAssigned = true;
|
||||||
|
|
||||||
@@ -129,6 +128,7 @@ export default function Page({ params }: { params: { id: string } }) {
|
|||||||
{protagonist.turret.guns.map((thisGun, index) => {
|
{protagonist.turret.guns.map((thisGun, index) => {
|
||||||
return (
|
return (
|
||||||
<ModuleButton
|
<ModuleButton
|
||||||
|
key={thisGun.id}
|
||||||
type="module"
|
type="module"
|
||||||
module="gun"
|
module="gun"
|
||||||
tier={thisGun.tier}
|
tier={thisGun.tier}
|
||||||
@@ -146,29 +146,6 @@ export default function Page({ params }: { params: { id: string } }) {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
// return (
|
|
||||||
// <Tooltip content={thisGun.name} key={thisGun.id}>
|
|
||||||
// <Button
|
|
||||||
// onClick={() => {
|
|
||||||
// mutateTankopedia((draft) => {
|
|
||||||
// if (!draft.areTanksAssigned) return;
|
|
||||||
// draft.protagonist.gun = thisGun;
|
|
||||||
// });
|
|
||||||
// }}
|
|
||||||
// variant={
|
|
||||||
// protagonist.gun.id === thisGun.id ? 'solid' : 'soft'
|
|
||||||
// }
|
|
||||||
// >
|
|
||||||
// <img
|
|
||||||
// src={asset('icons/modules/gun.webp')}
|
|
||||||
// width={32}
|
|
||||||
// height={32}
|
|
||||||
// />
|
|
||||||
// {TIER_ROMAN_NUMERALS[thisGun.tier]}
|
|
||||||
// </Button>
|
|
||||||
// </Tooltip>
|
|
||||||
// );
|
|
||||||
})}
|
})}
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
export class EventManager<Type> {
|
||||||
|
private callbacks: Set<(data: Type) => void> = new Set();
|
||||||
|
|
||||||
|
on(callback: (data: Type) => void) {
|
||||||
|
this.callbacks.add(callback);
|
||||||
|
}
|
||||||
|
off(callback: (data: Type) => void) {
|
||||||
|
this.callbacks.delete(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
emit(data: Type) {
|
||||||
|
this.callbacks.forEach((callback) => callback(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { EventManager } from './eventManager';
|
||||||
|
|
||||||
|
export interface ModelTransformEventData {
|
||||||
|
yaw?: number;
|
||||||
|
pitch: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const modelTransformEvent = new EventManager<ModelTransformEventData>();
|
||||||
@@ -29,9 +29,8 @@ type Tankopedia = {
|
|||||||
};
|
};
|
||||||
model: {
|
model: {
|
||||||
physical: {
|
physical: {
|
||||||
hullYaw: number;
|
yaw: number;
|
||||||
turretYaw: number;
|
pitch: number;
|
||||||
gunPitch: number;
|
|
||||||
};
|
};
|
||||||
visual: {
|
visual: {
|
||||||
controlsEnabled: boolean;
|
controlsEnabled: boolean;
|
||||||
@@ -74,9 +73,8 @@ export const useTankopedia = create<Tankopedia>()(
|
|||||||
},
|
},
|
||||||
model: {
|
model: {
|
||||||
physical: {
|
physical: {
|
||||||
gunPitch: 0,
|
yaw: 0,
|
||||||
hullYaw: 0,
|
pitch: 0,
|
||||||
turretYaw: 0,
|
|
||||||
},
|
},
|
||||||
visual: {
|
visual: {
|
||||||
controlsEnabled: true,
|
controlsEnabled: true,
|
||||||
|
|||||||
Reference in new issue
Block a user