feat: loading bar

This commit is contained in:
TrèsAbhi committed 2024-01-04 13:37:36 -06:00
1 parent 0a4e696bc1
commit 6ca9e637dc
5 files changed
+97 -120

No files matched your search

@@ -1,25 +1,18 @@
import { OrbitControls } from '@react-three/drei'; import { OrbitControls } from '@react-three/drei';
import { useThree } from '@react-three/fiber'; import { useThree } from '@react-three/fiber';
import { RefObject, useEffect, useRef } from 'react'; import { useEffect, useRef } from 'react';
import { Box3, Object3D } from 'three';
import { OrbitControls as OrbitControlsClass } from 'three-stdlib'; import { OrbitControls as OrbitControlsClass } from 'three-stdlib';
import { useTankopedia } from '../../../../../stores/tankopedia'; import { useTankopedia } from '../../../../../stores/tankopedia';
interface ControlsProps { export function Controls() {
fit: RefObject<Object3D>;
}
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); const enabled = useTankopedia((state) => state.model.controlsEnabled);
useEffect(() => { useEffect(() => {
const box = new Box3().setFromObject(fit.current!); camera.position.set(-5, 4, -20);
orbitControls.current?.target.set(0, 1.5, 0);
orbitControls.current?.target.set(0, (box.min.y + box.max.y) / 2, 0); }, []);
camera.position.set(-5, (box.min.y + box.max.y) / 2, -15);
}, [fit.current]);
return <OrbitControls ref={orbitControls} enabled={enabled} />; return <OrbitControls ref={orbitControls} enabled={enabled} />;
} }
@@ -0,0 +1,29 @@
import { Html, useProgress } from '@react-three/drei';
import { theme } from '../../../../../stitches.config';
export function Loader() {
const { progress } = useProgress();
return (
<Html center>
<div
style={{
width: 160,
height: 16,
backgroundColor: theme.colors.appBackground1_light,
borderRadius: 8,
overflow: 'hidden',
}}
>
<div
style={{
width: `${Math.round(progress)}%`,
height: '100%',
backgroundColor: theme.colors.solidBackground_purple,
transition: 'width 0.1s',
}}
></div>
</div>
</Html>
);
}
@@ -1,9 +1,18 @@
import { useLoader } from '@react-three/fiber'; import { useLoader } from '@react-three/fiber';
import { forwardRef, use, useImperativeHandle, useRef } from 'react'; import {
forwardRef,
useEffect,
useImperativeHandle,
useRef,
useState,
} from 'react';
import { Group, Vector3 } from 'three'; import { Group, Vector3 } from 'three';
import { GLTFLoader } from 'three-stdlib'; import { GLTFLoader } from 'three-stdlib';
import { X_AXIS } from '../../../../../constants/axis'; import { X_AXIS } from '../../../../../constants/axis';
import { modelDefinitions } from '../../../../../core/blitzkrieg/modelDefinitions'; import {
ModelDefinitions,
modelDefinitions,
} from '../../../../../core/blitzkrieg/modelDefinitions';
import mutateTankopedia, { import mutateTankopedia, {
useTankopedia, useTankopedia,
} from '../../../../../stores/tankopedia'; } from '../../../../../stores/tankopedia';
@@ -11,22 +20,33 @@ import { GunContainer } from './GunContainer';
import { HullContainer } from './HullContainer'; import { HullContainer } from './HullContainer';
import { TurretContainer } from './TurretContainer'; import { TurretContainer } from './TurretContainer';
interface ModelProps { interface TankModelProps {
tankId: number; tankId: number;
gunId: number;
turretId: number; turretId: number;
gunId: number;
} }
export const Model = forwardRef<Group, ModelProps>( export const TankModel = forwardRef<Group, TankModelProps>(
({ gunId, tankId, turretId }, ref) => { ({ tankId, turretId, gunId }, ref) => {
const awaitedModelDefinitions = use(modelDefinitions); // "TypeError: dispatcher.use is not a function"
const model = useTankopedia((state) => state.model); // const awaitedModelDefinitions = use(modelDefinitions);
const [awaitedModelDefinitions, setAwaitedModelDefinitions] = useState<
ModelDefinitions | undefined
>(undefined);
const gltf = useLoader(GLTFLoader, `/test/${tankId}.glb`); const gltf = useLoader(GLTFLoader, `/test/${tankId}.glb`);
const gunContainer = useRef<Group>(null); const model = useTankopedia((state) => state.model);
const hullContainer = useRef<Group>(null); const hullContainer = useRef<Group>(null);
const turretContainer = useRef<Group>(null); const turretContainer = useRef<Group>(null);
const gunContainer = useRef<Group>(null);
useImperativeHandle(ref, () => hullContainer.current!); useImperativeHandle(ref, () => hullContainer.current!);
useEffect(() => {
(async () => {
setAwaitedModelDefinitions(await modelDefinitions);
})();
}, []);
if (!awaitedModelDefinitions) return null;
const tankModelDefinition = awaitedModelDefinitions[tankId]; const tankModelDefinition = awaitedModelDefinitions[tankId];
const turretModelDefinition = tankModelDefinition.turrets[turretId]; const turretModelDefinition = tankModelDefinition.turrets[turretId];
@@ -48,24 +68,24 @@ export const Model = forwardRef<Group, ModelProps>(
yaw={model.hullYaw} yaw={model.hullYaw}
ref={hullContainer} ref={hullContainer}
onYawStart={() => onYawStart={() =>
mutateTankopedia((state) => { mutateTankopedia((draft) => {
state.model.controlsEnabled = false; draft.model.controlsEnabled = false;
}) })
} }
onYawEnd={(yaw) => { onYawEnd={(yaw) => {
mutateTankopedia((state) => { mutateTankopedia((draft) => {
state.model.controlsEnabled = true; draft.model.controlsEnabled = true;
state.model.hullYaw = yaw; draft.model.hullYaw = yaw;
}); });
}} }}
onTrackStart={() => { onTrackStart={() => {
mutateTankopedia((state) => { mutateTankopedia((draft) => {
state.model.controlsEnabled = false; draft.model.controlsEnabled = false;
}); });
}} }}
onTrackEnd={() => { onTrackEnd={() => {
mutateTankopedia((state) => { mutateTankopedia((draft) => {
state.model.controlsEnabled = true; draft.model.controlsEnabled = true;
}); });
}} }}
> >
+23 -90
View File
@@ -19,18 +19,16 @@ import {
Theme, Theme,
Tooltip, Tooltip,
} from '@radix-ui/themes'; } from '@radix-ui/themes';
import { Canvas, useLoader } from '@react-three/fiber'; import { Canvas } from '@react-three/fiber';
import { go } from 'fuzzysort'; import { go } from 'fuzzysort';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import Link from 'next/link'; import Link from 'next/link';
import { use, useRef, useState } from 'react'; import { Suspense, use, useRef, useState } from 'react';
import { Group, Vector3 } from 'three'; import { Group, Vector3 } from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import { Flag } from '../../../../components/Flag'; import { Flag } from '../../../../components/Flag';
import { ModuleButtons } from '../../../../components/ModuleButton'; import { ModuleButtons } from '../../../../components/ModuleButton';
import PageWrapper from '../../../../components/PageWrapper'; import PageWrapper from '../../../../components/PageWrapper';
import { asset } from '../../../../core/blitzkrieg/asset'; import { asset } from '../../../../core/blitzkrieg/asset';
import { modelDefinitions } from '../../../../core/blitzkrieg/modelDefinitions';
import { import {
TIER_ROMAN_NUMERALS, TIER_ROMAN_NUMERALS,
tankDefinitions, tankDefinitions,
@@ -38,30 +36,24 @@ import {
} from '../../../../core/blitzkrieg/tankDefinitions'; } from '../../../../core/blitzkrieg/tankDefinitions';
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 { Loader } from './components/Loader';
import { HullContainer } from './components/HullContainer';
import { RotationInputs } from './components/RotationInputs'; import { RotationInputs } from './components/RotationInputs';
import { SceneProps } from './components/SceneProps'; import { SceneProps } from './components/SceneProps';
import { TurretContainer } from './components/TurretContainer'; import { TankModel } from './components/TankModel';
const X_AXIS = new Vector3(1, 0, 0); const X_AXIS = new Vector3(1, 0, 0);
export default function Page({ params }: { params: { id: string } }) { export default function Page({ params }: { params: { id: string } }) {
const id = parseInt(params.id); const id = parseInt(params.id);
const awaitedTankDefinitions = use(tankDefinitions); const awaitedTankDefinitions = use(tankDefinitions);
const awaitedModelDefinitions = use(modelDefinitions);
const awaitedTankNamesDiacritics = use(tankNamesDiacritics); const awaitedTankNamesDiacritics = use(tankNamesDiacritics);
const tank = awaitedTankDefinitions[id]; const tank = awaitedTankDefinitions[id];
const tankModelDefinition = awaitedModelDefinitions[id];
const [turret, setTurret] = useState(tank.turrets.at(-1)!); const [turret, setTurret] = useState(tank.turrets.at(-1)!);
const turretModelDefinition = tankModelDefinition.turrets[turret.id];
const [gun, setGun] = useState(turret.guns.at(-1)!); const [gun, setGun] = useState(turret.guns.at(-1)!);
const gunModelDefinition = turretModelDefinition.guns[gun.id];
const [crew, setCrew] = useState(100); const [crew, setCrew] = useState(100);
const [mode, setMode] = useState('model'); const [mode, setMode] = useState('model');
const versusTankSearchInput = useRef<HTMLInputElement>(null); const versusTankSearchInput = useRef<HTMLInputElement>(null);
const hullContainer = useRef<Group>(null); const hullContainer = useRef<Group>(null);
const turretContainer = useRef<Group>(null);
const [versusTankSearchResults, setVersusTankSearchResults] = useState< const [versusTankSearchResults, setVersusTankSearchResults] = useState<
number[] number[]
>([]); >([]);
@@ -70,22 +62,6 @@ export default function Page({ params }: { params: { id: string } }) {
const [versusGun, setVersusGun] = useState(versusTurret.guns.at(-1)!); const [versusGun, setVersusGun] = useState(versusTurret.guns.at(-1)!);
const [versusTankTab, setVersusTankTab] = useState('search'); const [versusTankTab, setVersusTankTab] = useState('search');
const canvas = useRef<HTMLCanvasElement>(null); const canvas = useRef<HTMLCanvasElement>(null);
const gltf = useLoader(GLTFLoader, `/test/${id}.glb`);
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 [turretYaw, setTurretYaw] = useState(0);
const [hullYaw, setHullYaw] = useState(0);
const [gunPitch, setGunPitch] = useState(0);
const [controlsEnabled, setControlsEnabled] = useState(true);
const gunContainer = useRef<Group>(null);
function handlePointerDown() { function handlePointerDown() {
window.addEventListener('pointermove', handlePointerMove); window.addEventListener('pointermove', handlePointerMove);
@@ -138,63 +114,17 @@ export default function Page({ params }: { params: { id: string } }) {
camera={{ fov: 20 }} camera={{ fov: 20 }}
onPointerDown={handlePointerDown} onPointerDown={handlePointerDown}
> >
<Controls fit={hullContainer} /> <Controls />
<SceneProps />
<HullContainer <Suspense fallback={<Loader />}>
objects={gltf.scene.children[0].children} <SceneProps />
yaw={hullYaw} <TankModel
ref={hullContainer} gunId={gun.id}
onYawStart={() => setControlsEnabled(false)} tankId={tank.id}
onYawEnd={(yaw) => { turretId={turret.id}
setControlsEnabled(true); ref={hullContainer}
setHullYaw(yaw); />
}} </Suspense>
onTrackStart={() => setControlsEnabled(false)}
onTrackEnd={() => setControlsEnabled(true)}
>
<TurretContainer
initialTurretRotation={tankModelDefinition.turretRotation}
gunOrigin={gunOrigin}
ref={turretContainer}
objects={gltf.scene.children[0].children}
model={turretModelDefinition.model}
onYawStart={() => setControlsEnabled(false)}
yawLimits={turretModelDefinition.yaw}
pitchLimits={gunModelDefinition.pitch}
pitch={gunPitch}
onYawEnd={(pitch, yaw) => {
setControlsEnabled(true);
setGunPitch(pitch);
setTurretYaw(yaw);
}}
gunContainer={gunContainer}
turretOrigin={turretOrigin}
yaw={turretYaw}
>
<GunContainer
ref={gunContainer}
initialTurretRotation={
tankModelDefinition.turretRotation
}
onPitchStart={() => setControlsEnabled(false)}
pitchLimits={gunModelDefinition.pitch}
turretContainer={turretContainer}
yaw={turretYaw}
onPitchEnd={(pitch, yaw) => {
setControlsEnabled(true);
setGunPitch(pitch);
setTurretYaw(yaw);
}}
yawLimits={turretModelDefinition.yaw}
gunOrigin={gunOrigin}
pitch={gunPitch}
model={gunModelDefinition.model}
objects={gltf.scene.children[0].children}
turretOrigin={turretOrigin}
/>
</TurretContainer>
</HullContainer>
</Canvas> </Canvas>
</div> </div>
@@ -218,7 +148,7 @@ export default function Page({ params }: { params: { id: string } }) {
<Flex gap="1"> <Flex gap="1">
{tank.turrets.map((thisTurret) => { {tank.turrets.map((thisTurret) => {
return ( return (
<Tooltip content={thisTurret.name}> <Tooltip content={thisTurret.name} key={thisTurret.id}>
<Button <Button
onClick={() => { onClick={() => {
setTurret(thisTurret); setTurret(thisTurret);
@@ -239,9 +169,9 @@ export default function Page({ params }: { params: { id: string } }) {
</Flex> </Flex>
<Flex gap="1"> <Flex gap="1">
{turret.guns.map((thisGun, index) => { {turret.guns.map((thisGun) => {
return ( return (
<Tooltip content={thisGun.name}> <Tooltip content={thisGun.name} key={thisGun.id}>
<Button <Button
onClick={() => setGun(thisGun)} onClick={() => setGun(thisGun)}
variant={gun.id === thisGun.id ? 'solid' : 'soft'} variant={gun.id === thisGun.id ? 'solid' : 'soft'}
@@ -371,7 +301,10 @@ export default function Page({ params }: { params: { id: string } }) {
<Flex gap="2" wrap="wrap"> <Flex gap="2" wrap="wrap">
<Flex> <Flex>
{versusTank.turrets.map((turret, index) => ( {versusTank.turrets.map((turret, index) => (
<Tooltip content={turret.name}> <Tooltip
content={turret.name}
key={turret.id}
>
<ModuleButtons <ModuleButtons
key={id} key={id}
onClick={() => { onClick={() => {
@@ -404,7 +337,7 @@ export default function Page({ params }: { params: { id: string } }) {
</Flex> </Flex>
<Flex> <Flex>
{versusTurret.guns.map((gun, index) => ( {versusTurret.guns.map((gun, index) => (
<Tooltip content={gun.name}> <Tooltip content={gun.name} key={gun.id}>
<ModuleButtons <ModuleButtons
key={id} key={id}
onClick={() => setVersusGun(gun)} onClick={() => setVersusGun(gun)}
@@ -519,7 +452,7 @@ export default function Page({ params }: { params: { id: string } }) {
<Heading size="5">Shells</Heading> <Heading size="5">Shells</Heading>
{gun.shells.map((shell) => ( {gun.shells.map((shell) => (
<Text> <Text key={shell.id}>
{shell.name} ({shell.type}): {shell.damage.armor} {shell.name} ({shell.type}): {shell.damage.armor}
</Text> </Text>
))} ))}
+2
View File
@@ -2,6 +2,7 @@ import {
amberDark, amberDark,
blueDark, blueDark,
orangeDark, orangeDark,
purpleDark,
slate, slate,
slateDark, slateDark,
slateDarkA, slateDarkA,
@@ -20,6 +21,7 @@ export const theme = {
...createColors(amberDark, 'amber'), ...createColors(amberDark, 'amber'),
...createColors(orangeDark, 'orange'), ...createColors(orangeDark, 'orange'),
...createColors(purpleDark, 'purple'),
...createColors(blueDark, 'blue'), ...createColors(blueDark, 'blue'),
...createColors(tealDark, 'teal'), ...createColors(tealDark, 'teal'),
}, },