mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 02:37:05 +02:00
feat: loading bar
This commit is contained in:
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 { useThree } from '@react-three/fiber';
|
||||
import { RefObject, useEffect, useRef } from 'react';
|
||||
import { Box3, Object3D } from 'three';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { OrbitControls as OrbitControlsClass } from 'three-stdlib';
|
||||
import { useTankopedia } from '../../../../../stores/tankopedia';
|
||||
|
||||
interface ControlsProps {
|
||||
fit: RefObject<Object3D>;
|
||||
}
|
||||
|
||||
export function Controls({ fit }: ControlsProps) {
|
||||
export function Controls() {
|
||||
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!);
|
||||
|
||||
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]);
|
||||
camera.position.set(-5, 4, -20);
|
||||
orbitControls.current?.target.set(0, 1.5, 0);
|
||||
}, []);
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
+38
-18
@@ -1,9 +1,18 @@
|
||||
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 { GLTFLoader } from 'three-stdlib';
|
||||
import { X_AXIS } from '../../../../../constants/axis';
|
||||
import { modelDefinitions } from '../../../../../core/blitzkrieg/modelDefinitions';
|
||||
import {
|
||||
ModelDefinitions,
|
||||
modelDefinitions,
|
||||
} from '../../../../../core/blitzkrieg/modelDefinitions';
|
||||
import mutateTankopedia, {
|
||||
useTankopedia,
|
||||
} from '../../../../../stores/tankopedia';
|
||||
@@ -11,22 +20,33 @@ import { GunContainer } from './GunContainer';
|
||||
import { HullContainer } from './HullContainer';
|
||||
import { TurretContainer } from './TurretContainer';
|
||||
|
||||
interface ModelProps {
|
||||
interface TankModelProps {
|
||||
tankId: number;
|
||||
gunId: number;
|
||||
turretId: number;
|
||||
gunId: number;
|
||||
}
|
||||
|
||||
export const Model = forwardRef<Group, ModelProps>(
|
||||
({ gunId, tankId, turretId }, ref) => {
|
||||
const awaitedModelDefinitions = use(modelDefinitions);
|
||||
const model = useTankopedia((state) => state.model);
|
||||
export const TankModel = forwardRef<Group, TankModelProps>(
|
||||
({ tankId, turretId, gunId }, ref) => {
|
||||
// "TypeError: dispatcher.use is not a function"
|
||||
// const awaitedModelDefinitions = use(modelDefinitions);
|
||||
const [awaitedModelDefinitions, setAwaitedModelDefinitions] = useState<
|
||||
ModelDefinitions | undefined
|
||||
>(undefined);
|
||||
const gltf = useLoader(GLTFLoader, `/test/${tankId}.glb`);
|
||||
const gunContainer = useRef<Group>(null);
|
||||
const model = useTankopedia((state) => state.model);
|
||||
const hullContainer = useRef<Group>(null);
|
||||
const turretContainer = useRef<Group>(null);
|
||||
const gunContainer = useRef<Group>(null);
|
||||
|
||||
useImperativeHandle(ref, () => hullContainer.current!);
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
setAwaitedModelDefinitions(await modelDefinitions);
|
||||
})();
|
||||
}, []);
|
||||
|
||||
if (!awaitedModelDefinitions) return null;
|
||||
|
||||
const tankModelDefinition = awaitedModelDefinitions[tankId];
|
||||
const turretModelDefinition = tankModelDefinition.turrets[turretId];
|
||||
@@ -48,24 +68,24 @@ export const Model = forwardRef<Group, ModelProps>(
|
||||
yaw={model.hullYaw}
|
||||
ref={hullContainer}
|
||||
onYawStart={() =>
|
||||
mutateTankopedia((state) => {
|
||||
state.model.controlsEnabled = false;
|
||||
mutateTankopedia((draft) => {
|
||||
draft.model.controlsEnabled = false;
|
||||
})
|
||||
}
|
||||
onYawEnd={(yaw) => {
|
||||
mutateTankopedia((state) => {
|
||||
state.model.controlsEnabled = true;
|
||||
state.model.hullYaw = yaw;
|
||||
mutateTankopedia((draft) => {
|
||||
draft.model.controlsEnabled = true;
|
||||
draft.model.hullYaw = yaw;
|
||||
});
|
||||
}}
|
||||
onTrackStart={() => {
|
||||
mutateTankopedia((state) => {
|
||||
state.model.controlsEnabled = false;
|
||||
mutateTankopedia((draft) => {
|
||||
draft.model.controlsEnabled = false;
|
||||
});
|
||||
}}
|
||||
onTrackEnd={() => {
|
||||
mutateTankopedia((state) => {
|
||||
state.model.controlsEnabled = true;
|
||||
mutateTankopedia((draft) => {
|
||||
draft.model.controlsEnabled = true;
|
||||
});
|
||||
}}
|
||||
>
|
||||
@@ -19,18 +19,16 @@ import {
|
||||
Theme,
|
||||
Tooltip,
|
||||
} from '@radix-ui/themes';
|
||||
import { Canvas, useLoader } from '@react-three/fiber';
|
||||
import { Canvas } from '@react-three/fiber';
|
||||
import { go } from 'fuzzysort';
|
||||
import { debounce } from 'lodash';
|
||||
import Link from 'next/link';
|
||||
import { use, useRef, useState } from 'react';
|
||||
import { Suspense, use, useRef, useState } from 'react';
|
||||
import { Group, Vector3 } from 'three';
|
||||
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
|
||||
import { Flag } from '../../../../components/Flag';
|
||||
import { ModuleButtons } from '../../../../components/ModuleButton';
|
||||
import PageWrapper from '../../../../components/PageWrapper';
|
||||
import { asset } from '../../../../core/blitzkrieg/asset';
|
||||
import { modelDefinitions } from '../../../../core/blitzkrieg/modelDefinitions';
|
||||
import {
|
||||
TIER_ROMAN_NUMERALS,
|
||||
tankDefinitions,
|
||||
@@ -38,30 +36,24 @@ import {
|
||||
} from '../../../../core/blitzkrieg/tankDefinitions';
|
||||
import * as styles from '../page.css';
|
||||
import { Controls } from './components/Control';
|
||||
import { GunContainer } from './components/GunContainer';
|
||||
import { HullContainer } from './components/HullContainer';
|
||||
import { Loader } from './components/Loader';
|
||||
import { RotationInputs } from './components/RotationInputs';
|
||||
import { SceneProps } from './components/SceneProps';
|
||||
import { TurretContainer } from './components/TurretContainer';
|
||||
import { TankModel } from './components/TankModel';
|
||||
|
||||
const X_AXIS = new Vector3(1, 0, 0);
|
||||
|
||||
export default function Page({ params }: { params: { id: string } }) {
|
||||
const id = parseInt(params.id);
|
||||
const awaitedTankDefinitions = use(tankDefinitions);
|
||||
const awaitedModelDefinitions = use(modelDefinitions);
|
||||
const awaitedTankNamesDiacritics = use(tankNamesDiacritics);
|
||||
const tank = awaitedTankDefinitions[id];
|
||||
const tankModelDefinition = awaitedModelDefinitions[id];
|
||||
const [turret, setTurret] = useState(tank.turrets.at(-1)!);
|
||||
const turretModelDefinition = tankModelDefinition.turrets[turret.id];
|
||||
const [gun, setGun] = useState(turret.guns.at(-1)!);
|
||||
const gunModelDefinition = turretModelDefinition.guns[gun.id];
|
||||
const [crew, setCrew] = useState(100);
|
||||
const [mode, setMode] = useState('model');
|
||||
const versusTankSearchInput = useRef<HTMLInputElement>(null);
|
||||
const hullContainer = useRef<Group>(null);
|
||||
const turretContainer = useRef<Group>(null);
|
||||
const [versusTankSearchResults, setVersusTankSearchResults] = useState<
|
||||
number[]
|
||||
>([]);
|
||||
@@ -70,22 +62,6 @@ export default function Page({ params }: { params: { id: string } }) {
|
||||
const [versusGun, setVersusGun] = useState(versusTurret.guns.at(-1)!);
|
||||
const [versusTankTab, setVersusTankTab] = useState('search');
|
||||
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() {
|
||||
window.addEventListener('pointermove', handlePointerMove);
|
||||
@@ -138,63 +114,17 @@ export default function Page({ params }: { params: { id: string } }) {
|
||||
camera={{ fov: 20 }}
|
||||
onPointerDown={handlePointerDown}
|
||||
>
|
||||
<Controls fit={hullContainer} />
|
||||
<SceneProps />
|
||||
<Controls />
|
||||
|
||||
<HullContainer
|
||||
objects={gltf.scene.children[0].children}
|
||||
yaw={hullYaw}
|
||||
ref={hullContainer}
|
||||
onYawStart={() => setControlsEnabled(false)}
|
||||
onYawEnd={(yaw) => {
|
||||
setControlsEnabled(true);
|
||||
setHullYaw(yaw);
|
||||
}}
|
||||
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>
|
||||
<Suspense fallback={<Loader />}>
|
||||
<SceneProps />
|
||||
<TankModel
|
||||
gunId={gun.id}
|
||||
tankId={tank.id}
|
||||
turretId={turret.id}
|
||||
ref={hullContainer}
|
||||
/>
|
||||
</Suspense>
|
||||
</Canvas>
|
||||
</div>
|
||||
|
||||
@@ -218,7 +148,7 @@ export default function Page({ params }: { params: { id: string } }) {
|
||||
<Flex gap="1">
|
||||
{tank.turrets.map((thisTurret) => {
|
||||
return (
|
||||
<Tooltip content={thisTurret.name}>
|
||||
<Tooltip content={thisTurret.name} key={thisTurret.id}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setTurret(thisTurret);
|
||||
@@ -239,9 +169,9 @@ export default function Page({ params }: { params: { id: string } }) {
|
||||
</Flex>
|
||||
|
||||
<Flex gap="1">
|
||||
{turret.guns.map((thisGun, index) => {
|
||||
{turret.guns.map((thisGun) => {
|
||||
return (
|
||||
<Tooltip content={thisGun.name}>
|
||||
<Tooltip content={thisGun.name} key={thisGun.id}>
|
||||
<Button
|
||||
onClick={() => setGun(thisGun)}
|
||||
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>
|
||||
{versusTank.turrets.map((turret, index) => (
|
||||
<Tooltip content={turret.name}>
|
||||
<Tooltip
|
||||
content={turret.name}
|
||||
key={turret.id}
|
||||
>
|
||||
<ModuleButtons
|
||||
key={id}
|
||||
onClick={() => {
|
||||
@@ -404,7 +337,7 @@ export default function Page({ params }: { params: { id: string } }) {
|
||||
</Flex>
|
||||
<Flex>
|
||||
{versusTurret.guns.map((gun, index) => (
|
||||
<Tooltip content={gun.name}>
|
||||
<Tooltip content={gun.name} key={gun.id}>
|
||||
<ModuleButtons
|
||||
key={id}
|
||||
onClick={() => setVersusGun(gun)}
|
||||
@@ -519,7 +452,7 @@ export default function Page({ params }: { params: { id: string } }) {
|
||||
<Heading size="5">Shells</Heading>
|
||||
|
||||
{gun.shells.map((shell) => (
|
||||
<Text>
|
||||
<Text key={shell.id}>
|
||||
{shell.name} ({shell.type}): {shell.damage.armor}
|
||||
</Text>
|
||||
))}
|
||||
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
amberDark,
|
||||
blueDark,
|
||||
orangeDark,
|
||||
purpleDark,
|
||||
slate,
|
||||
slateDark,
|
||||
slateDarkA,
|
||||
@@ -20,6 +21,7 @@ export const theme = {
|
||||
|
||||
...createColors(amberDark, 'amber'),
|
||||
...createColors(orangeDark, 'orange'),
|
||||
...createColors(purpleDark, 'purple'),
|
||||
...createColors(blueDark, 'blue'),
|
||||
...createColors(tealDark, 'teal'),
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user