feat: code split

This commit is contained in:
TrèsAbhi committed 2024-01-04 13:06:40 -06:00
1 parent 2cf0bda352
commit 0a4e696bc1
15 files changed
+308 -121

No files matched your search

BIN
View File
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.
+3 -2
View File
@@ -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>
);
}
+9 -117
View File
@@ -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>
+5
View File
@@ -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);
+12
View File
@@ -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) {