mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 02:37:05 +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",
|
||||
"metallicness",
|
||||
"MIPMAPCOUNT",
|
||||
"modeltransform",
|
||||
"MURICA",
|
||||
"NEGATIVEX",
|
||||
"NEGATIVEY",
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Card, Flex, Tabs, Theme } from '@radix-ui/themes';
|
||||
import { Html } from '@react-three/drei';
|
||||
import { Canvas } from '@react-three/fiber';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { Suspense, use, useEffect, useRef, useState } from 'react';
|
||||
import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawLimits';
|
||||
import { modelDefinitions } from '../../../../../../core/blitzkrieg/modelDefinitions';
|
||||
@@ -20,7 +19,6 @@ import { TankArmor } from './components/TankArmor';
|
||||
import { TankModel } from './components/TankModel';
|
||||
|
||||
export function TankDisplay() {
|
||||
const router = useRouter();
|
||||
const canvas = useRef<HTMLCanvasElement>(null);
|
||||
const awaitedModelDefinitions = use(modelDefinitions);
|
||||
const canvasWrapper = useRef<HTMLDivElement>(null);
|
||||
@@ -66,24 +64,16 @@ export function TankDisplay() {
|
||||
const gunModelDefinition = turretModelDefinition.guns[protagonist.gun.id];
|
||||
|
||||
mutateTankopedia((draft) => {
|
||||
[draft.model.physical.gunPitch, draft.model.physical.turretYaw] =
|
||||
[draft.model.physical.pitch, draft.model.physical.yaw] =
|
||||
applyPitchYawLimits(
|
||||
draft.model.physical.gunPitch,
|
||||
draft.model.physical.turretYaw,
|
||||
draft.model.physical.pitch,
|
||||
draft.model.physical.yaw,
|
||||
gunModelDefinition.pitch,
|
||||
turretModelDefinition.yaw,
|
||||
);
|
||||
});
|
||||
}, [protagonist?.gun, protagonist?.turret]);
|
||||
|
||||
useEffect(() => {
|
||||
if (location.hash.startsWith('#')) {
|
||||
mutateTankopedia((draft) => {
|
||||
draft.mode = location.hash.match(/#(.+)/)![1] as TankopediaMode;
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
if (!protagonist) return null;
|
||||
|
||||
return (
|
||||
@@ -107,8 +97,6 @@ export function TankDisplay() {
|
||||
<Tabs.Root
|
||||
value={mode}
|
||||
onValueChange={(mode) => {
|
||||
router.push(`#${mode}`);
|
||||
|
||||
mutateTankopedia((draft) => {
|
||||
draft.mode = mode as TankopediaMode;
|
||||
});
|
||||
|
||||
@@ -2,14 +2,19 @@ import { GroupProps, useLoader } from '@react-three/fiber';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { Euler, Group, Mesh, Vector3 } from 'three';
|
||||
import { GLTFLoader } from 'three-stdlib';
|
||||
import { degToRad } from 'three/src/math/MathUtils';
|
||||
import { ArmorMesh } from '../../../../../../../components/ArmorMesh';
|
||||
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 {
|
||||
ModelDefinitions,
|
||||
modelDefinitions,
|
||||
} from '../../../../../../../core/blitzkrieg/modelDefinitions';
|
||||
import {
|
||||
ModelTransformEventData,
|
||||
modelTransformEvent,
|
||||
} from '../../../../../../../core/blitzkrieg/modelTransform';
|
||||
import { nameToArmorId } from '../../../../../../../core/blitzkrieg/nameToArmorId';
|
||||
import { resolveArmor } from '../../../../../../../core/blitzkrieg/resolveThickness';
|
||||
import { useTankopedia } from '../../../../../../../stores/tankopedia';
|
||||
@@ -17,7 +22,6 @@ import { useTankopedia } from '../../../../../../../stores/tankopedia';
|
||||
interface TankArmorProps extends GroupProps {}
|
||||
|
||||
export function TankArmor({ ...props }: TankArmorProps) {
|
||||
const mode = useTankopedia((state) => state.mode);
|
||||
const wrapper = useRef<Group>(null);
|
||||
const [awaitedModelDefinitions, setAwaitedModelDefinitions] = useState<
|
||||
ModelDefinitions | undefined
|
||||
@@ -29,7 +33,9 @@ export function TankArmor({ ...props }: TankArmorProps) {
|
||||
const showSpacedArmor = useTankopedia(
|
||||
(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(() => {
|
||||
(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
|
||||
// it's more for typescript to stop throwing a fit
|
||||
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]);
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribe = useTankopedia.subscribe(
|
||||
(state) => state.mode,
|
||||
(mode) => {
|
||||
if (wrapper.current) wrapper.current.visible = mode === 'armor';
|
||||
},
|
||||
);
|
||||
|
||||
return unsubscribe;
|
||||
});
|
||||
|
||||
if (!protagonist) return null;
|
||||
|
||||
const armorGltf = useLoader(
|
||||
@@ -68,34 +149,14 @@ export function TankArmor({ ...props }: TankArmorProps) {
|
||||
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 (
|
||||
<HeadsUpDisplay>
|
||||
<group
|
||||
{...props}
|
||||
visible={mode === 'armor'}
|
||||
ref={wrapper}
|
||||
rotation={[-Math.PI / 2, 0, physical.hullYaw]}
|
||||
rotation={[-Math.PI / 2, 0, 0]}
|
||||
visible={initialTankopediaState.mode === 'armor'}
|
||||
>
|
||||
{armorNodes.map((node) => {
|
||||
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) => {
|
||||
const isCurrentTurret = node.name.startsWith(
|
||||
`turret_${turretModelDefinition.model
|
||||
@@ -173,15 +234,7 @@ export function TankArmor({ ...props }: TankArmorProps) {
|
||||
/>
|
||||
);
|
||||
})}
|
||||
<group
|
||||
position={new Vector3()
|
||||
.sub(turretOrigin)
|
||||
.sub(gunOrigin)
|
||||
.applyAxisAngle(new Vector3(1, 0, 0), physical.gunPitch)
|
||||
.add(turretOrigin)
|
||||
.add(gunOrigin)}
|
||||
rotation={[physical.gunPitch, 0, 0]}
|
||||
>
|
||||
<group ref={gunContainer}>
|
||||
{armorNodes.map((node) => {
|
||||
const isCurrentGun = node.name.startsWith(
|
||||
`gun_${gunModelDefinition.model.toString().padStart(2, '0')}`,
|
||||
|
||||
@@ -9,13 +9,18 @@ import {
|
||||
Vector3,
|
||||
} from 'three';
|
||||
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 { asset } from '../../../../../../../core/blitzkrieg/asset';
|
||||
import {
|
||||
ModelDefinitions,
|
||||
modelDefinitions,
|
||||
} from '../../../../../../../core/blitzkrieg/modelDefinitions';
|
||||
import {
|
||||
ModelTransformEventData,
|
||||
modelTransformEvent,
|
||||
} from '../../../../../../../core/blitzkrieg/modelTransform';
|
||||
import { resolveJsxTree } from '../../../../../../../core/blitzkrieg/resolveJsxTree';
|
||||
import { normalizeAnglePI } from '../../../../../../../core/math/normalizeAngle180';
|
||||
import mutateTankopedia, {
|
||||
@@ -28,11 +33,14 @@ export function TankModel() {
|
||||
const [awaitedModelDefinitions, setAwaitedModelDefinitions] = useState<
|
||||
ModelDefinitions | undefined
|
||||
>(undefined);
|
||||
const canvas = useThree((state) => state.gl.domElement);
|
||||
const protagonist = useTankopedia((state) => {
|
||||
if (!state.areTanksAssigned) return;
|
||||
return state.protagonist;
|
||||
});
|
||||
|
||||
if (!protagonist) return null;
|
||||
|
||||
const canvas = useThree((state) => state.gl.domElement);
|
||||
const physical = useTankopedia((state) => state.model.physical);
|
||||
const hullContainer = 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(
|
||||
GLTFLoader,
|
||||
@@ -52,51 +131,13 @@ export function TankModel() {
|
||||
);
|
||||
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 (
|
||||
<group ref={hullContainer} rotation={[-Math.PI / 2, 0, physical.hullYaw]}>
|
||||
<group ref={hullContainer} rotation={[-Math.PI / 2, 0, 0]}>
|
||||
{nodes.map((node) => {
|
||||
const isHull = node.name === 'hull';
|
||||
const isWheel = node.name.startsWith('chassis_wheel_');
|
||||
const isTrack = node.name.startsWith('chassis_track_');
|
||||
const isVisible = isHull || isWheel || isTrack;
|
||||
let draftHullYaw = 0;
|
||||
|
||||
function handlePointerDown(event: ThreeEvent<PointerEvent>) {
|
||||
event.stopPropagation();
|
||||
@@ -104,31 +145,12 @@ export function TankModel() {
|
||||
draft.model.visual.controlsEnabled = false;
|
||||
});
|
||||
|
||||
if (isHull) {
|
||||
draftHullYaw = physical.hullYaw;
|
||||
|
||||
window.addEventListener('pointermove', handlePointerMoveHull);
|
||||
window.addEventListener('pointerup', handlePointerUpHull);
|
||||
} else if (isTrack) {
|
||||
window.addEventListener('pointermove', handlePointerMoveTrack);
|
||||
window.addEventListener('pointerup', handlePointerUpTrack);
|
||||
if (isTrack) {
|
||||
window.addEventListener('pointermove', handlePointerMove);
|
||||
window.addEventListener('pointerup', handlePointerUp);
|
||||
}
|
||||
}
|
||||
function handlePointerMoveHull(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) {
|
||||
function handlePointerMove(event: PointerEvent) {
|
||||
const mesh = node as Mesh;
|
||||
const material = mesh.material as MeshStandardMaterial;
|
||||
const offset = new Vector2(
|
||||
@@ -141,12 +163,12 @@ export function TankModel() {
|
||||
material.roughnessMap?.offset.add(offset);
|
||||
material.normalMap?.offset.add(offset);
|
||||
}
|
||||
function handlePointerUpTrack() {
|
||||
function handlePointerUp() {
|
||||
mutateTankopedia((draft) => {
|
||||
draft.model.visual.controlsEnabled = true;
|
||||
});
|
||||
window.removeEventListener('pointermove', handlePointerMoveTrack);
|
||||
window.removeEventListener('pointerup', handlePointerUpTrack);
|
||||
window.removeEventListener('pointermove', handlePointerMove);
|
||||
window.removeEventListener('pointerup', handlePointerUp);
|
||||
}
|
||||
|
||||
if (!isVisible) return null;
|
||||
@@ -167,26 +189,22 @@ export function TankModel() {
|
||||
);
|
||||
})}
|
||||
|
||||
<group
|
||||
position={turretPosition}
|
||||
rotation={turretRotation}
|
||||
ref={turretContainer}
|
||||
>
|
||||
<group ref={turretContainer}>
|
||||
{nodes.map((node) => {
|
||||
const isTurret = node.name.startsWith('turret_');
|
||||
const isCurrentTurret =
|
||||
node.name ===
|
||||
`turret_${turretModelDefinition.model.toString().padStart(2, '0')}`;
|
||||
const isVisible = isCurrentTurret;
|
||||
let draftPitch = 0;
|
||||
let draftYaw = 0;
|
||||
let pitch = 0;
|
||||
let yaw = 0;
|
||||
|
||||
function handlePointerDown(event: ThreeEvent<PointerEvent>) {
|
||||
event.stopPropagation();
|
||||
|
||||
if (isTurret) {
|
||||
draftYaw = physical.turretYaw;
|
||||
draftPitch = physical.gunPitch;
|
||||
yaw = physical.yaw;
|
||||
pitch = physical.pitch;
|
||||
|
||||
mutateTankopedia((draft) => {
|
||||
draft.model.visual.controlsEnabled = false;
|
||||
@@ -196,53 +214,19 @@ export function TankModel() {
|
||||
}
|
||||
}
|
||||
function handlePointerMove(event: PointerEvent) {
|
||||
[draftPitch, draftYaw] = applyPitchYawLimits(
|
||||
draftPitch,
|
||||
draftYaw + event.movementX * (Math.PI / canvas.width),
|
||||
[pitch, yaw] = applyPitchYawLimits(
|
||||
pitch,
|
||||
yaw + event.movementX * (Math.PI / canvas.width),
|
||||
gunModelDefinition.pitch,
|
||||
turretModelDefinition.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);
|
||||
}
|
||||
modelTransformEvent.emit({ pitch, yaw });
|
||||
}
|
||||
function handlePointerUp() {
|
||||
mutateTankopedia((state) => {
|
||||
state.model.visual.controlsEnabled = true;
|
||||
state.model.physical.gunPitch = normalizeAnglePI(draftPitch);
|
||||
state.model.physical.turretYaw = normalizeAnglePI(draftYaw);
|
||||
state.model.physical.pitch = normalizeAnglePI(pitch);
|
||||
state.model.physical.yaw = normalizeAnglePI(yaw);
|
||||
});
|
||||
window.removeEventListener('pointermove', handlePointerMove);
|
||||
window.removeEventListener('pointerup', handlePointerUp);
|
||||
@@ -267,16 +251,7 @@ export function TankModel() {
|
||||
);
|
||||
})}
|
||||
|
||||
<group
|
||||
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}
|
||||
>
|
||||
<group ref={gunContainer}>
|
||||
{nodes.map((node) => {
|
||||
const isCurrentMantlet =
|
||||
node.name ===
|
||||
@@ -287,8 +262,8 @@ export function TankModel() {
|
||||
node.name ===
|
||||
`gun_${gunModelDefinition.model.toString().padStart(2, '0')}`;
|
||||
const isVisible = isCurrentGun || isCurrentMantlet;
|
||||
let draftPitch = 0;
|
||||
let draftYaw = 0;
|
||||
let pitch = 0;
|
||||
let yaw = 0;
|
||||
|
||||
function handlePointerDown(event: ThreeEvent<PointerEvent>) {
|
||||
event.stopPropagation();
|
||||
@@ -296,59 +271,25 @@ export function TankModel() {
|
||||
mutateTankopedia((draft) => {
|
||||
draft.model.visual.controlsEnabled = false;
|
||||
});
|
||||
draftPitch = physical.gunPitch;
|
||||
draftYaw = physical.turretYaw;
|
||||
pitch = physical.pitch;
|
||||
yaw = physical.yaw;
|
||||
window.addEventListener('pointermove', handlePointerMove);
|
||||
window.addEventListener('pointerup', handlePointerUp);
|
||||
}
|
||||
function handlePointerMove(event: PointerEvent) {
|
||||
[draftPitch, draftYaw] = applyPitchYawLimits(
|
||||
draftPitch - event.movementY * (Math.PI / canvas.height),
|
||||
draftYaw + event.movementX * (Math.PI / canvas.width),
|
||||
[pitch, yaw] = applyPitchYawLimits(
|
||||
pitch - event.movementY * (Math.PI / canvas.height),
|
||||
yaw + event.movementX * (Math.PI / canvas.width),
|
||||
gunModelDefinition.pitch,
|
||||
turretModelDefinition.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);
|
||||
}
|
||||
modelTransformEvent.emit({ pitch, yaw });
|
||||
}
|
||||
function handlePointerUp() {
|
||||
mutateTankopedia((state) => {
|
||||
state.model.visual.controlsEnabled = true;
|
||||
state.model.physical.gunPitch = normalizeAnglePI(draftPitch);
|
||||
state.model.physical.turretYaw = normalizeAnglePI(draftYaw);
|
||||
state.model.physical.pitch = normalizeAnglePI(pitch);
|
||||
state.model.physical.yaw = normalizeAnglePI(yaw);
|
||||
});
|
||||
window.removeEventListener('pointermove', handlePointerMove);
|
||||
window.removeEventListener('pointerup', handlePointerUp);
|
||||
|
||||
@@ -2,7 +2,6 @@ import { Flex, TextField } from '@radix-ui/themes';
|
||||
import { use, useEffect, useRef } from 'react';
|
||||
import { applyPitchYawLimits } from '../../../../../core/blitz/applyPitchYawLimits';
|
||||
import { modelDefinitions } from '../../../../../core/blitzkrieg/modelDefinitions';
|
||||
import { normalizeAnglePI } from '../../../../../core/math/normalizeAngle180';
|
||||
import mutateTankopedia, {
|
||||
useTankopedia,
|
||||
} from '../../../../../stores/tankopedia';
|
||||
@@ -11,28 +10,22 @@ export function RotationInputs() {
|
||||
const physical = useTankopedia((state) => state.model.physical);
|
||||
const awaitedModelDefinitions = use(modelDefinitions);
|
||||
const turretYawInput = useRef<HTMLInputElement>(null);
|
||||
const hullYawInput = useRef<HTMLInputElement>(null);
|
||||
const gunPitchInput = useRef<HTMLInputElement>(null);
|
||||
const protagonist = useTankopedia((state) => {
|
||||
if (!state.areTanksAssigned) return;
|
||||
return state.protagonist;
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
hullYawInput.current!.value = `${-Math.round(
|
||||
physical.hullYaw * (180 / Math.PI),
|
||||
)}`;
|
||||
}, [physical.hullYaw]);
|
||||
useEffect(() => {
|
||||
turretYawInput.current!.value = `${-Math.round(
|
||||
physical.turretYaw * (180 / Math.PI),
|
||||
physical.yaw * (180 / Math.PI),
|
||||
)}`;
|
||||
}, [physical.turretYaw]);
|
||||
}, [physical.yaw]);
|
||||
useEffect(() => {
|
||||
gunPitchInput.current!.value = `${-Math.round(
|
||||
physical.gunPitch * (180 / Math.PI),
|
||||
physical.pitch * (180 / Math.PI),
|
||||
)}`;
|
||||
}, [physical.gunPitch]);
|
||||
}, [physical.pitch]);
|
||||
|
||||
if (!protagonist) return null;
|
||||
|
||||
@@ -55,38 +48,9 @@ export function RotationInputs() {
|
||||
<Flex
|
||||
gap="2"
|
||||
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
|
||||
style={{
|
||||
flex: 1,
|
||||
@@ -94,17 +58,17 @@ export function RotationInputs() {
|
||||
>
|
||||
<TextField.Slot>Turret</TextField.Slot>
|
||||
<TextField.Input
|
||||
defaultValue={Math.round(physical.turretYaw * (180 / Math.PI))}
|
||||
defaultValue={Math.round(physical.yaw * (180 / Math.PI))}
|
||||
onBlur={() => {
|
||||
const [pitch, yaw] = applyPitchYawLimits(
|
||||
physical.gunPitch,
|
||||
physical.pitch,
|
||||
-Number(turretYawInput.current?.value) * (Math.PI / 180),
|
||||
gunModelDefinition.pitch,
|
||||
turretModelDefinition.yaw,
|
||||
);
|
||||
mutateTankopedia((state) => {
|
||||
state.model.physical.gunPitch = pitch;
|
||||
state.model.physical.turretYaw = yaw;
|
||||
state.model.physical.pitch = pitch;
|
||||
state.model.physical.yaw = yaw;
|
||||
});
|
||||
turretYawInput.current!.value = `${Math.round(
|
||||
yaw * (180 / Math.PI),
|
||||
@@ -129,17 +93,17 @@ export function RotationInputs() {
|
||||
>
|
||||
<TextField.Slot>Gun</TextField.Slot>
|
||||
<TextField.Input
|
||||
defaultValue={-Math.round(physical.gunPitch * (180 / Math.PI))}
|
||||
defaultValue={-Math.round(physical.pitch * (180 / Math.PI))}
|
||||
onBlur={() => {
|
||||
const [pitch, yaw] = applyPitchYawLimits(
|
||||
-Number(gunPitchInput.current?.value) * (Math.PI / 180),
|
||||
physical.turretYaw,
|
||||
physical.yaw,
|
||||
gunModelDefinition.pitch,
|
||||
turretModelDefinition.yaw,
|
||||
);
|
||||
mutateTankopedia((state) => {
|
||||
state.model.physical.gunPitch = pitch;
|
||||
state.model.physical.turretYaw = yaw;
|
||||
state.model.physical.pitch = pitch;
|
||||
state.model.physical.yaw = yaw;
|
||||
});
|
||||
gunPitchInput.current!.value = `${Math.round(
|
||||
pitch * (180 / Math.PI),
|
||||
|
||||
@@ -18,9 +18,8 @@ export default function Page({ params }: { params: { id: string } }) {
|
||||
|
||||
useEffect(() => {
|
||||
mutateTankopedia((draft) => {
|
||||
draft.model.physical.hullYaw = 0;
|
||||
draft.model.physical.turretYaw = 0;
|
||||
draft.model.physical.gunPitch = 0;
|
||||
draft.model.physical.yaw = 0;
|
||||
draft.model.physical.pitch = 0;
|
||||
draft.mode = 'model';
|
||||
draft.areTanksAssigned = true;
|
||||
|
||||
@@ -129,6 +128,7 @@ export default function Page({ params }: { params: { id: string } }) {
|
||||
{protagonist.turret.guns.map((thisGun, index) => {
|
||||
return (
|
||||
<ModuleButton
|
||||
key={thisGun.id}
|
||||
type="module"
|
||||
module="gun"
|
||||
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>
|
||||
|
||||
@@ -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: {
|
||||
physical: {
|
||||
hullYaw: number;
|
||||
turretYaw: number;
|
||||
gunPitch: number;
|
||||
yaw: number;
|
||||
pitch: number;
|
||||
};
|
||||
visual: {
|
||||
controlsEnabled: boolean;
|
||||
@@ -74,9 +73,8 @@ export const useTankopedia = create<Tankopedia>()(
|
||||
},
|
||||
model: {
|
||||
physical: {
|
||||
gunPitch: 0,
|
||||
hullYaw: 0,
|
||||
turretYaw: 0,
|
||||
yaw: 0,
|
||||
pitch: 0,
|
||||
},
|
||||
visual: {
|
||||
controlsEnabled: true,
|
||||
|
||||
Reference in new issue
Block a user