mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 18:57:03 +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 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 !== 16401) continue; // waffle
|
||||
// if (id !== 7425) continue; // isu 152
|
||||
@@ -600,9 +600,10 @@ if (allTargets || targets?.includes('tankModels')) {
|
||||
// if (id !== 7297) continue; // 60tp
|
||||
// if (id !== 1) continue; // t-34
|
||||
// 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 !== 6225) continue; // fv215b
|
||||
if (id !== 4481) continue; // kran
|
||||
|
||||
console.log(`Building model ${id} @ ${nation}/${tankIndex}`);
|
||||
|
||||
|
||||
@@ -3,15 +3,16 @@ import { useThree } from '@react-three/fiber';
|
||||
import { RefObject, useEffect, useRef } from 'react';
|
||||
import { Box3, Object3D } from 'three';
|
||||
import { OrbitControls as OrbitControlsClass } from 'three-stdlib';
|
||||
import { useTankopedia } from '../../../../../stores/tankopedia';
|
||||
|
||||
interface ControlsProps {
|
||||
enabled: boolean;
|
||||
fit: RefObject<Object3D>;
|
||||
}
|
||||
|
||||
export function Controls({ enabled, fit }: ControlsProps) {
|
||||
export function Controls({ fit }: ControlsProps) {
|
||||
const camera = useThree((state) => state.camera);
|
||||
const orbitControls = useRef<OrbitControlsClass>(null);
|
||||
const enabled = useTankopedia((state) => state.model.controlsEnabled);
|
||||
|
||||
useEffect(() => {
|
||||
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';
|
||||
import { Canvas, useLoader } from '@react-three/fiber';
|
||||
import { go } from 'fuzzysort';
|
||||
import { clamp, debounce } from 'lodash';
|
||||
import { debounce } from 'lodash';
|
||||
import Link from 'next/link';
|
||||
import { use, useEffect, useRef, useState } from 'react';
|
||||
import { use, useRef, useState } from 'react';
|
||||
import { Group, Vector3 } from 'three';
|
||||
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
|
||||
import { Flag } from '../../../../components/Flag';
|
||||
@@ -36,11 +36,11 @@ import {
|
||||
tankDefinitions,
|
||||
tankNamesDiacritics,
|
||||
} from '../../../../core/blitzkrieg/tankDefinitions';
|
||||
import { normalizeAnglePI } from '../../../../core/math/normalizeAngle180';
|
||||
import * as styles from '../page.css';
|
||||
import { Controls } from './components/Control';
|
||||
import { GunContainer } from './components/GunContainer';
|
||||
import { HullContainer } from './components/HullContainer';
|
||||
import { RotationInputs } from './components/RotationInputs';
|
||||
import { SceneProps } from './components/SceneProps';
|
||||
import { TurretContainer } from './components/TurretContainer';
|
||||
|
||||
@@ -85,23 +85,8 @@ export default function Page({ params }: { params: { id: string } }) {
|
||||
const [hullYaw, setHullYaw] = useState(0);
|
||||
const [gunPitch, setGunPitch] = useState(0);
|
||||
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);
|
||||
|
||||
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() {
|
||||
window.addEventListener('pointermove', handlePointerMove);
|
||||
window.addEventListener('pointerup', handlePointerUp);
|
||||
@@ -153,7 +138,7 @@ export default function Page({ params }: { params: { id: string } }) {
|
||||
camera={{ fov: 20 }}
|
||||
onPointerDown={handlePointerDown}
|
||||
>
|
||||
<Controls enabled={controlsEnabled} fit={hullContainer} />
|
||||
<Controls fit={hullContainer} />
|
||||
<SceneProps />
|
||||
|
||||
<HullContainer
|
||||
@@ -220,104 +205,11 @@ export default function Page({ params }: { params: { id: string } }) {
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
<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(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>
|
||||
<RotationInputs
|
||||
gunId={gun.id}
|
||||
tankId={tank.id}
|
||||
turretId={turret.id}
|
||||
/>
|
||||
</Flex>
|
||||
</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[];
|
||||
test: TankopediaTestTankDisplay;
|
||||
};
|
||||
model: {
|
||||
hullYaw: number;
|
||||
turretYaw: number;
|
||||
gunPitch: number;
|
||||
controlsEnabled: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
export const useTankopedia = create<Tankopedia>()(() => ({
|
||||
@@ -33,6 +39,12 @@ export const useTankopedia = create<Tankopedia>()(() => ({
|
||||
nations: [],
|
||||
test: 'include',
|
||||
},
|
||||
model: {
|
||||
gunPitch: 0,
|
||||
hullYaw: 0,
|
||||
turretYaw: 0,
|
||||
controlsEnabled: true,
|
||||
},
|
||||
}));
|
||||
|
||||
export default function mutateTankopedia(recipe: (draft: Tankopedia) => void) {
|
||||
|
||||
Reference in new issue
Block a user