mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-23 11:39:50 +02:00
feat: code split
This commit is contained in:
1 parent
2cf0bda352
commit
0a4e696bc1
15 files changed
+308
-121
No files matched your search
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -591,7 +591,7 @@ if (allTargets || targets?.includes('tankModels')) {
|
|||||||
const nationVehicleId = tank.id;
|
const nationVehicleId = tank.id;
|
||||||
const id = (nationVehicleId << 8) + (NATION_IDS[nation] << 4) + 1;
|
const id = (nationVehicleId << 8) + (NATION_IDS[nation] << 4) + 1;
|
||||||
|
|
||||||
// if (id !== 15697) continue; // chieftain TODO: investigate vertices stream over read
|
// if (id !== 15697) continue; // chieftain
|
||||||
// if (id !== 24609) continue; // concept 1b
|
// if (id !== 24609) continue; // concept 1b
|
||||||
// if (id !== 16401) continue; // waffle
|
// if (id !== 16401) continue; // waffle
|
||||||
// if (id !== 7425) continue; // isu 152
|
// if (id !== 7425) continue; // isu 152
|
||||||
@@ -600,9 +600,10 @@ if (allTargets || targets?.includes('tankModels')) {
|
|||||||
// if (id !== 7297) continue; // 60tp
|
// if (id !== 7297) continue; // 60tp
|
||||||
// if (id !== 1) continue; // t-34
|
// if (id !== 1) continue; // t-34
|
||||||
// if (id !== 6753) continue; // type 71
|
// if (id !== 6753) continue; // type 71
|
||||||
if (id !== 5137) continue; // tiger ii
|
// if (id !== 5137) continue; // tiger ii
|
||||||
// if (id !== 11633) continue; // forest witch
|
// if (id !== 11633) continue; // forest witch
|
||||||
// if (id !== 6225) continue; // fv215b
|
// if (id !== 6225) continue; // fv215b
|
||||||
|
if (id !== 4481) continue; // kran
|
||||||
|
|
||||||
console.log(`Building model ${id} @ ${nation}/${tankIndex}`);
|
console.log(`Building model ${id} @ ${nation}/${tankIndex}`);
|
||||||
|
|
||||||
|
|||||||
@@ -3,15 +3,16 @@ import { useThree } from '@react-three/fiber';
|
|||||||
import { RefObject, useEffect, useRef } from 'react';
|
import { RefObject, useEffect, useRef } from 'react';
|
||||||
import { Box3, Object3D } from 'three';
|
import { Box3, Object3D } from 'three';
|
||||||
import { OrbitControls as OrbitControlsClass } from 'three-stdlib';
|
import { OrbitControls as OrbitControlsClass } from 'three-stdlib';
|
||||||
|
import { useTankopedia } from '../../../../../stores/tankopedia';
|
||||||
|
|
||||||
interface ControlsProps {
|
interface ControlsProps {
|
||||||
enabled: boolean;
|
|
||||||
fit: RefObject<Object3D>;
|
fit: RefObject<Object3D>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Controls({ enabled, fit }: ControlsProps) {
|
export function Controls({ fit }: ControlsProps) {
|
||||||
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(() => {
|
||||||
const box = new Box3().setFromObject(fit.current!);
|
const box = new Box3().setFromObject(fit.current!);
|
||||||
|
|||||||
@@ -0,0 +1,126 @@
|
|||||||
|
import { useLoader } from '@react-three/fiber';
|
||||||
|
import { forwardRef, use, useImperativeHandle, useRef } from 'react';
|
||||||
|
import { Group, Vector3 } from 'three';
|
||||||
|
import { GLTFLoader } from 'three-stdlib';
|
||||||
|
import { X_AXIS } from '../../../../../constants/axis';
|
||||||
|
import { modelDefinitions } from '../../../../../core/blitzkrieg/modelDefinitions';
|
||||||
|
import mutateTankopedia, {
|
||||||
|
useTankopedia,
|
||||||
|
} from '../../../../../stores/tankopedia';
|
||||||
|
import { GunContainer } from './GunContainer';
|
||||||
|
import { HullContainer } from './HullContainer';
|
||||||
|
import { TurretContainer } from './TurretContainer';
|
||||||
|
|
||||||
|
interface ModelProps {
|
||||||
|
tankId: number;
|
||||||
|
gunId: number;
|
||||||
|
turretId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Model = forwardRef<Group, ModelProps>(
|
||||||
|
({ gunId, tankId, turretId }, ref) => {
|
||||||
|
const awaitedModelDefinitions = use(modelDefinitions);
|
||||||
|
const model = useTankopedia((state) => state.model);
|
||||||
|
const gltf = useLoader(GLTFLoader, `/test/${tankId}.glb`);
|
||||||
|
const gunContainer = useRef<Group>(null);
|
||||||
|
const hullContainer = useRef<Group>(null);
|
||||||
|
const turretContainer = useRef<Group>(null);
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => hullContainer.current!);
|
||||||
|
|
||||||
|
const tankModelDefinition = awaitedModelDefinitions[tankId];
|
||||||
|
const turretModelDefinition = tankModelDefinition.turrets[turretId];
|
||||||
|
const gunModelDefinition = turretModelDefinition.guns[gunId];
|
||||||
|
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);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<HullContainer
|
||||||
|
objects={gltf.scene.children[0].children}
|
||||||
|
yaw={model.hullYaw}
|
||||||
|
ref={hullContainer}
|
||||||
|
onYawStart={() =>
|
||||||
|
mutateTankopedia((state) => {
|
||||||
|
state.model.controlsEnabled = false;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
onYawEnd={(yaw) => {
|
||||||
|
mutateTankopedia((state) => {
|
||||||
|
state.model.controlsEnabled = true;
|
||||||
|
state.model.hullYaw = yaw;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
onTrackStart={() => {
|
||||||
|
mutateTankopedia((state) => {
|
||||||
|
state.model.controlsEnabled = false;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
onTrackEnd={() => {
|
||||||
|
mutateTankopedia((state) => {
|
||||||
|
state.model.controlsEnabled = true;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TurretContainer
|
||||||
|
initialTurretRotation={tankModelDefinition.turretRotation}
|
||||||
|
gunOrigin={gunOrigin}
|
||||||
|
ref={turretContainer}
|
||||||
|
objects={gltf.scene.children[0].children}
|
||||||
|
model={turretModelDefinition.model}
|
||||||
|
onYawStart={() =>
|
||||||
|
mutateTankopedia((state) => {
|
||||||
|
state.model.controlsEnabled = false;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
yawLimits={turretModelDefinition.yaw}
|
||||||
|
pitchLimits={gunModelDefinition.pitch}
|
||||||
|
pitch={model.gunPitch}
|
||||||
|
onYawEnd={(pitch, yaw) => {
|
||||||
|
mutateTankopedia((state) => {
|
||||||
|
state.model.controlsEnabled = true;
|
||||||
|
state.model.gunPitch = pitch;
|
||||||
|
state.model.turretYaw = yaw;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
gunContainer={gunContainer}
|
||||||
|
turretOrigin={turretOrigin}
|
||||||
|
yaw={model.turretYaw}
|
||||||
|
>
|
||||||
|
<GunContainer
|
||||||
|
ref={gunContainer}
|
||||||
|
initialTurretRotation={tankModelDefinition.turretRotation}
|
||||||
|
onPitchStart={() => {
|
||||||
|
mutateTankopedia((state) => {
|
||||||
|
state.model.controlsEnabled = false;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
pitchLimits={gunModelDefinition.pitch}
|
||||||
|
turretContainer={turretContainer}
|
||||||
|
yaw={model.turretYaw}
|
||||||
|
onPitchEnd={(pitch, yaw) => {
|
||||||
|
mutateTankopedia((state) => {
|
||||||
|
state.model.controlsEnabled = true;
|
||||||
|
state.model.gunPitch = pitch;
|
||||||
|
state.model.turretYaw = yaw;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
yawLimits={turretModelDefinition.yaw}
|
||||||
|
gunOrigin={gunOrigin}
|
||||||
|
pitch={model.gunPitch}
|
||||||
|
model={gunModelDefinition.model}
|
||||||
|
objects={gltf.scene.children[0].children}
|
||||||
|
turretOrigin={turretOrigin}
|
||||||
|
/>
|
||||||
|
</TurretContainer>
|
||||||
|
</HullContainer>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
import { Flex, TextField } from '@radix-ui/themes';
|
||||||
|
import { clamp } from 'lodash';
|
||||||
|
import { use, useEffect, useRef } from 'react';
|
||||||
|
import { modelDefinitions } from '../../../../../core/blitzkrieg/modelDefinitions';
|
||||||
|
import { normalizeAnglePI } from '../../../../../core/math/normalizeAngle180';
|
||||||
|
import mutateTankopedia, {
|
||||||
|
useTankopedia,
|
||||||
|
} from '../../../../../stores/tankopedia';
|
||||||
|
|
||||||
|
interface RotationInputsProps {
|
||||||
|
tankId: number;
|
||||||
|
gunId: number;
|
||||||
|
turretId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function RotationInputs({
|
||||||
|
tankId,
|
||||||
|
gunId,
|
||||||
|
turretId,
|
||||||
|
}: RotationInputsProps) {
|
||||||
|
const model = useTankopedia((state) => state.model);
|
||||||
|
const awaitedModelDefinitions = use(modelDefinitions);
|
||||||
|
const turretYawInput = useRef<HTMLInputElement>(null);
|
||||||
|
const hullYawInput = useRef<HTMLInputElement>(null);
|
||||||
|
const gunPitchInput = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
const tankModelDefinition = awaitedModelDefinitions[tankId];
|
||||||
|
const turretModelDefinition = tankModelDefinition.turrets[turretId];
|
||||||
|
const gunModelDefinition = turretModelDefinition.guns[gunId];
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
hullYawInput.current!.value = `${-Math.round(
|
||||||
|
model.hullYaw * (180 / Math.PI),
|
||||||
|
)}`;
|
||||||
|
}, [model.hullYaw]);
|
||||||
|
useEffect(() => {
|
||||||
|
turretYawInput.current!.value = `${-Math.round(
|
||||||
|
model.turretYaw * (180 / Math.PI),
|
||||||
|
)}`;
|
||||||
|
}, [model.turretYaw]);
|
||||||
|
useEffect(() => {
|
||||||
|
gunPitchInput.current!.value = `${-Math.round(
|
||||||
|
model.gunPitch * (180 / Math.PI),
|
||||||
|
)}`;
|
||||||
|
}, [model.gunPitch]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
gap="2"
|
||||||
|
align="center"
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
left: 16,
|
||||||
|
bottom: 16,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TextField.Root
|
||||||
|
variant="surface"
|
||||||
|
style={{
|
||||||
|
width: 115,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TextField.Slot>Hull</TextField.Slot>
|
||||||
|
<TextField.Input
|
||||||
|
defaultValue={Math.round(model.hullYaw * (180 / Math.PI))}
|
||||||
|
onBlur={() => {
|
||||||
|
const normalized = normalizeAnglePI(
|
||||||
|
-Number(hullYawInput.current?.value) * (Math.PI / 180),
|
||||||
|
);
|
||||||
|
mutateTankopedia((state) => {
|
||||||
|
state.model.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
|
||||||
|
variant="surface"
|
||||||
|
style={{
|
||||||
|
width: 115,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TextField.Slot>Turret</TextField.Slot>
|
||||||
|
<TextField.Input
|
||||||
|
defaultValue={Math.round(model.turretYaw * (180 / Math.PI))}
|
||||||
|
onBlur={() => {
|
||||||
|
const normalized = normalizeAnglePI(
|
||||||
|
-Number(turretYawInput.current?.value) * (Math.PI / 180),
|
||||||
|
);
|
||||||
|
mutateTankopedia((state) => {
|
||||||
|
state.model.turretYaw = normalized;
|
||||||
|
});
|
||||||
|
turretYawInput.current!.value = `${Math.round(normalized)}`;
|
||||||
|
}}
|
||||||
|
onKeyDown={(event) => {
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
turretYawInput.current?.blur();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onFocus={() => turretYawInput.current?.focus()}
|
||||||
|
ref={turretYawInput}
|
||||||
|
style={{ textAlign: 'right' }}
|
||||||
|
/>
|
||||||
|
<TextField.Slot>°</TextField.Slot>
|
||||||
|
</TextField.Root>
|
||||||
|
|
||||||
|
<TextField.Root
|
||||||
|
variant="surface"
|
||||||
|
style={{
|
||||||
|
width: 115,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TextField.Slot>Gun</TextField.Slot>
|
||||||
|
<TextField.Input
|
||||||
|
defaultValue={-Math.round(model.gunPitch * (180 / Math.PI))}
|
||||||
|
onBlur={() => {
|
||||||
|
const clamped = clamp(
|
||||||
|
Number(gunPitchInput.current?.value),
|
||||||
|
gunModelDefinition.pitch.min,
|
||||||
|
gunModelDefinition.pitch.max,
|
||||||
|
);
|
||||||
|
mutateTankopedia((state) => {
|
||||||
|
state.model.gunPitch = -clamped * (Math.PI / 180);
|
||||||
|
});
|
||||||
|
gunPitchInput.current!.value = `${Math.round(clamped)}`;
|
||||||
|
}}
|
||||||
|
onKeyDown={(event) => {
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
gunPitchInput.current?.blur();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onFocus={() => gunPitchInput.current?.focus()}
|
||||||
|
ref={gunPitchInput}
|
||||||
|
style={{ textAlign: 'right' }}
|
||||||
|
/>
|
||||||
|
<TextField.Slot>°</TextField.Slot>
|
||||||
|
</TextField.Root>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -21,9 +21,9 @@ import {
|
|||||||
} from '@radix-ui/themes';
|
} from '@radix-ui/themes';
|
||||||
import { Canvas, useLoader } from '@react-three/fiber';
|
import { Canvas, useLoader } from '@react-three/fiber';
|
||||||
import { go } from 'fuzzysort';
|
import { go } from 'fuzzysort';
|
||||||
import { clamp, debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { use, useEffect, useRef, useState } from 'react';
|
import { use, useRef, useState } from 'react';
|
||||||
import { Group, Vector3 } from 'three';
|
import { Group, Vector3 } from 'three';
|
||||||
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
|
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
|
||||||
import { Flag } from '../../../../components/Flag';
|
import { Flag } from '../../../../components/Flag';
|
||||||
@@ -36,11 +36,11 @@ import {
|
|||||||
tankDefinitions,
|
tankDefinitions,
|
||||||
tankNamesDiacritics,
|
tankNamesDiacritics,
|
||||||
} from '../../../../core/blitzkrieg/tankDefinitions';
|
} from '../../../../core/blitzkrieg/tankDefinitions';
|
||||||
import { normalizeAnglePI } from '../../../../core/math/normalizeAngle180';
|
|
||||||
import * as styles from '../page.css';
|
import * as styles from '../page.css';
|
||||||
import { Controls } from './components/Control';
|
import { Controls } from './components/Control';
|
||||||
import { GunContainer } from './components/GunContainer';
|
import { GunContainer } from './components/GunContainer';
|
||||||
import { HullContainer } from './components/HullContainer';
|
import { HullContainer } from './components/HullContainer';
|
||||||
|
import { RotationInputs } from './components/RotationInputs';
|
||||||
import { SceneProps } from './components/SceneProps';
|
import { SceneProps } from './components/SceneProps';
|
||||||
import { TurretContainer } from './components/TurretContainer';
|
import { TurretContainer } from './components/TurretContainer';
|
||||||
|
|
||||||
@@ -85,23 +85,8 @@ export default function Page({ params }: { params: { id: string } }) {
|
|||||||
const [hullYaw, setHullYaw] = useState(0);
|
const [hullYaw, setHullYaw] = useState(0);
|
||||||
const [gunPitch, setGunPitch] = useState(0);
|
const [gunPitch, setGunPitch] = useState(0);
|
||||||
const [controlsEnabled, setControlsEnabled] = useState(true);
|
const [controlsEnabled, setControlsEnabled] = useState(true);
|
||||||
const turretYawInput = useRef<HTMLInputElement>(null);
|
|
||||||
const hullYawInput = useRef<HTMLInputElement>(null);
|
|
||||||
const gunPitchInput = useRef<HTMLInputElement>(null);
|
|
||||||
const gunContainer = useRef<Group>(null);
|
const gunContainer = useRef<Group>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
hullYawInput.current!.value = `${-Math.round(hullYaw * (180 / Math.PI))}`;
|
|
||||||
}, [hullYaw]);
|
|
||||||
useEffect(() => {
|
|
||||||
turretYawInput.current!.value = `${-Math.round(
|
|
||||||
turretYaw * (180 / Math.PI),
|
|
||||||
)}`;
|
|
||||||
}, [turretYaw]);
|
|
||||||
useEffect(() => {
|
|
||||||
gunPitchInput.current!.value = `${-Math.round(gunPitch * (180 / Math.PI))}`;
|
|
||||||
}, [gunPitch]);
|
|
||||||
|
|
||||||
function handlePointerDown() {
|
function handlePointerDown() {
|
||||||
window.addEventListener('pointermove', handlePointerMove);
|
window.addEventListener('pointermove', handlePointerMove);
|
||||||
window.addEventListener('pointerup', handlePointerUp);
|
window.addEventListener('pointerup', handlePointerUp);
|
||||||
@@ -153,7 +138,7 @@ export default function Page({ params }: { params: { id: string } }) {
|
|||||||
camera={{ fov: 20 }}
|
camera={{ fov: 20 }}
|
||||||
onPointerDown={handlePointerDown}
|
onPointerDown={handlePointerDown}
|
||||||
>
|
>
|
||||||
<Controls enabled={controlsEnabled} fit={hullContainer} />
|
<Controls fit={hullContainer} />
|
||||||
<SceneProps />
|
<SceneProps />
|
||||||
|
|
||||||
<HullContainer
|
<HullContainer
|
||||||
@@ -220,104 +205,11 @@ export default function Page({ params }: { params: { id: string } }) {
|
|||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Flex
|
<RotationInputs
|
||||||
gap="2"
|
gunId={gun.id}
|
||||||
align="center"
|
tankId={tank.id}
|
||||||
style={{
|
turretId={turret.id}
|
||||||
position: 'absolute',
|
/>
|
||||||
left: 16,
|
|
||||||
bottom: 16,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<TextField.Root
|
|
||||||
variant="surface"
|
|
||||||
style={{
|
|
||||||
width: 115,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<TextField.Slot>Hull</TextField.Slot>
|
|
||||||
<TextField.Input
|
|
||||||
defaultValue={Math.round(hullYaw * (180 / Math.PI))}
|
|
||||||
onBlur={() => {
|
|
||||||
const normalized = normalizeAnglePI(
|
|
||||||
-Number(hullYawInput.current?.value) * (Math.PI / 180),
|
|
||||||
);
|
|
||||||
setHullYaw(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
|
|
||||||
variant="surface"
|
|
||||||
style={{
|
|
||||||
width: 115,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<TextField.Slot>Turret</TextField.Slot>
|
|
||||||
<TextField.Input
|
|
||||||
defaultValue={Math.round(turretYaw * (180 / Math.PI))}
|
|
||||||
onBlur={() => {
|
|
||||||
const normalized = normalizeAnglePI(
|
|
||||||
-Number(turretYawInput.current?.value) *
|
|
||||||
(Math.PI / 180),
|
|
||||||
);
|
|
||||||
setTurretYaw(normalized);
|
|
||||||
turretYawInput.current!.value = `${Math.round(
|
|
||||||
normalized,
|
|
||||||
)}`;
|
|
||||||
}}
|
|
||||||
onKeyDown={(event) => {
|
|
||||||
if (event.key === 'Enter') {
|
|
||||||
turretYawInput.current?.blur();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onFocus={() => turretYawInput.current?.focus()}
|
|
||||||
ref={turretYawInput}
|
|
||||||
style={{ textAlign: 'right' }}
|
|
||||||
/>
|
|
||||||
<TextField.Slot>°</TextField.Slot>
|
|
||||||
</TextField.Root>
|
|
||||||
|
|
||||||
<TextField.Root
|
|
||||||
variant="surface"
|
|
||||||
style={{
|
|
||||||
width: 115,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<TextField.Slot>Gun</TextField.Slot>
|
|
||||||
<TextField.Input
|
|
||||||
defaultValue={-Math.round(gunPitch * (180 / Math.PI))}
|
|
||||||
onBlur={() => {
|
|
||||||
const clamped = clamp(
|
|
||||||
Number(gunPitchInput.current?.value),
|
|
||||||
gunModelDefinition.pitch.min,
|
|
||||||
gunModelDefinition.pitch.max,
|
|
||||||
);
|
|
||||||
setGunPitch(-clamped * (Math.PI / 180));
|
|
||||||
gunPitchInput.current!.value = `${Math.round(clamped)}`;
|
|
||||||
}}
|
|
||||||
onKeyDown={(event) => {
|
|
||||||
if (event.key === 'Enter') {
|
|
||||||
gunPitchInput.current?.blur();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onFocus={() => gunPitchInput.current?.focus()}
|
|
||||||
ref={gunPitchInput}
|
|
||||||
style={{ textAlign: 'right' }}
|
|
||||||
/>
|
|
||||||
<TextField.Slot>°</TextField.Slot>
|
|
||||||
</TextField.Root>
|
|
||||||
</Flex>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { Vector3 } from 'three';
|
||||||
|
|
||||||
|
export const X_AXIS = new Vector3(1, 0, 0);
|
||||||
|
export const Y_AXIS = new Vector3(0, 1, 0);
|
||||||
|
export const Z_AXIS = new Vector3(0, 0, 1);
|
||||||
@@ -19,6 +19,12 @@ type Tankopedia = {
|
|||||||
nations: string[];
|
nations: string[];
|
||||||
test: TankopediaTestTankDisplay;
|
test: TankopediaTestTankDisplay;
|
||||||
};
|
};
|
||||||
|
model: {
|
||||||
|
hullYaw: number;
|
||||||
|
turretYaw: number;
|
||||||
|
gunPitch: number;
|
||||||
|
controlsEnabled: boolean;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useTankopedia = create<Tankopedia>()(() => ({
|
export const useTankopedia = create<Tankopedia>()(() => ({
|
||||||
@@ -33,6 +39,12 @@ export const useTankopedia = create<Tankopedia>()(() => ({
|
|||||||
nations: [],
|
nations: [],
|
||||||
test: 'include',
|
test: 'include',
|
||||||
},
|
},
|
||||||
|
model: {
|
||||||
|
gunPitch: 0,
|
||||||
|
hullYaw: 0,
|
||||||
|
turretYaw: 0,
|
||||||
|
controlsEnabled: true,
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export default function mutateTankopedia(recipe: (draft: Tankopedia) => void) {
|
export default function mutateTankopedia(recipe: (draft: Tankopedia) => void) {
|
||||||
|
|||||||
Reference in new issue
Block a user