mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-11-13 20:56:11 +01:00
feat: code split
This commit is contained in:
1 parent
2cf0bda352
commit
0a4e696bc1
15 files changed
+308
-121
No files matched your search
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user