mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-24 20:19:49 +02:00
feat: tank armor position stateful
This commit is contained in:
1 parent
9e0c4ddb9a
commit
6239a72bce
7 files changed
+125
-62
No files matched your search
@@ -28,8 +28,8 @@ export async function buildTankArmors() {
|
|||||||
const tank = tanks.root[tankKey];
|
const tank = tanks.root[tankKey];
|
||||||
const id = toUniqueId(nation, tank.id);
|
const id = toUniqueId(nation, tank.id);
|
||||||
|
|
||||||
if (id !== 7297) continue; // 60tp
|
// if (id !== 7297) continue; // 60tp
|
||||||
// if (id !== 5137) continue; // tiger ii
|
if (id !== 5137) continue; // tiger ii
|
||||||
// if (id !== 24609) continue; // concept 1b
|
// if (id !== 24609) continue; // concept 1b
|
||||||
|
|
||||||
console.log(`Building armor ${id} @ ${nation}/${tankKey}`);
|
console.log(`Building armor ${id} @ ${nation}/${tankKey}`);
|
||||||
|
|||||||
@@ -49,10 +49,10 @@ export async function buildTankModels() {
|
|||||||
// if (id !== 7425) continue; // isu 152
|
// if (id !== 7425) continue; // isu 152
|
||||||
// if (id !== 10369) continue; // mino
|
// if (id !== 10369) continue; // mino
|
||||||
// if (id !== 4417) continue; // amx m4 mle
|
// if (id !== 4417) continue; // amx m4 mle
|
||||||
if (id !== 7297) continue; // 60tp
|
// if (id !== 7297) continue; // 60tp
|
||||||
// if (id !== 1) continue; // t-34
|
// if (id !== 1) continue; // t-34
|
||||||
// if (id !== 6753) continue; // type 71
|
// 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 !== 11633) continue; // forest witch
|
||||||
// if (id !== 6225) continue; // fv215b
|
// if (id !== 6225) continue; // fv215b
|
||||||
// if (id !== 4481) continue; // kran
|
// if (id !== 4481) continue; // kran
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { Button, Card, Flex, Tabs, Theme } from '@radix-ui/themes';
|
|||||||
import { Html } from '@react-three/drei';
|
import { Html } from '@react-three/drei';
|
||||||
import { Canvas } from '@react-three/fiber';
|
import { Canvas } from '@react-three/fiber';
|
||||||
import { Suspense, use, useEffect, useRef, useState } from 'react';
|
import { Suspense, use, useEffect, useRef, useState } from 'react';
|
||||||
import { Group } from 'three';
|
|
||||||
import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawLimits';
|
import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawLimits';
|
||||||
import { modelDefinitions } from '../../../../../../core/blitzkrieg/modelDefinitions';
|
import { modelDefinitions } from '../../../../../../core/blitzkrieg/modelDefinitions';
|
||||||
import { theme } from '../../../../../../stitches.config';
|
import { theme } from '../../../../../../stitches.config';
|
||||||
@@ -29,7 +28,6 @@ export function TankDisplay() {
|
|||||||
const canvasWrapper = useRef<HTMLDivElement>(null);
|
const canvasWrapper = useRef<HTMLDivElement>(null);
|
||||||
const [isFullScreen, setIsFullScreen] = useState(false);
|
const [isFullScreen, setIsFullScreen] = useState(false);
|
||||||
const mode = useTankopedia((state) => state.mode);
|
const mode = useTankopedia((state) => state.mode);
|
||||||
const hullContainer = useRef<Group>(null);
|
|
||||||
const showGrid = useTankopedia((state) => state.model.showGrid);
|
const showGrid = useTankopedia((state) => state.model.showGrid);
|
||||||
const protagonist = useTankopedia((state) => {
|
const protagonist = useTankopedia((state) => {
|
||||||
if (!state.areTanksAssigned) return;
|
if (!state.areTanksAssigned) return;
|
||||||
@@ -134,8 +132,8 @@ export function TankDisplay() {
|
|||||||
</Html>
|
</Html>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<TankModel ref={hullContainer} />
|
<TankModel />
|
||||||
<TankArmor />
|
{mode === 'armor' && <TankArmor />}
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</Canvas>
|
</Canvas>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,65 +1,140 @@
|
|||||||
import { forwardRef } from 'react';
|
import { useLoader } from '@react-three/fiber';
|
||||||
import { Group } from 'three';
|
import { useEffect, useState } from 'react';
|
||||||
|
import { Euler, Mesh, Vector3 } from 'three';
|
||||||
|
import { GLTFLoader } from 'three-stdlib';
|
||||||
|
import { ArmorMesh } from '../../../../../../../components/ArmorMesh';
|
||||||
import { HeadsUpDisplay } from '../../../../../../../components/HeadsUpDisplay';
|
import { HeadsUpDisplay } from '../../../../../../../components/HeadsUpDisplay';
|
||||||
|
import { X_AXIS } from '../../../../../../../constants/axis';
|
||||||
|
import { asset } from '../../../../../../../core/blitzkrieg/asset';
|
||||||
|
import {
|
||||||
|
ModelDefinitions,
|
||||||
|
modelDefinitions,
|
||||||
|
} from '../../../../../../../core/blitzkrieg/modelDefinitions';
|
||||||
|
import { useTankopedia } from '../../../../../../../stores/tankopedia';
|
||||||
|
|
||||||
interface TankArmorProps {}
|
interface TankArmorProps {}
|
||||||
|
|
||||||
export const TankArmor = forwardRef<Group, TankArmorProps>((_props, ref) => {
|
export function TankArmor() {
|
||||||
|
const [awaitedModelDefinitions, setAwaitedModelDefinitions] = useState<
|
||||||
|
ModelDefinitions | undefined
|
||||||
|
>(undefined);
|
||||||
|
const protagonist = useTankopedia((state) => {
|
||||||
|
if (!state.areTanksAssigned) return;
|
||||||
|
return state.protagonist;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!protagonist) return null;
|
||||||
|
|
||||||
|
const gltf = useLoader(
|
||||||
|
GLTFLoader,
|
||||||
|
asset(`3d/tanks/armor/${protagonist.tank.id}.glb`),
|
||||||
|
);
|
||||||
|
const model = useTankopedia((state) => state.model);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
setAwaitedModelDefinitions(await modelDefinitions);
|
||||||
|
})();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!awaitedModelDefinitions) return null;
|
||||||
|
|
||||||
|
const nodes = Object.values(gltf.nodes);
|
||||||
|
const tankModelDefinition = awaitedModelDefinitions[protagonist.tank.id];
|
||||||
|
const turretModelDefinition =
|
||||||
|
tankModelDefinition.turrets[protagonist.turret.id];
|
||||||
|
const gunModelDefinition = turretModelDefinition.guns[protagonist.gun.id];
|
||||||
|
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 turretPosition = new Vector3()
|
||||||
|
.sub(turretOrigin)
|
||||||
|
.applyAxisAngle(new Vector3(0, 0, 1), model.turretYaw);
|
||||||
|
const turretRotation = new Euler(0, 0, model.turretYaw);
|
||||||
|
|
||||||
|
if (tankModelDefinition.turretRotation) {
|
||||||
|
const pitch = -tankModelDefinition.turretRotation.pitch * (Math.PI / 180);
|
||||||
|
const yaw = -tankModelDefinition.turretRotation.yaw * (Math.PI / 180);
|
||||||
|
const roll = -tankModelDefinition.turretRotation.roll * (Math.PI / 180);
|
||||||
|
|
||||||
|
turretPosition
|
||||||
|
.applyAxisAngle(new Vector3(1, 0, 0), pitch)
|
||||||
|
.applyAxisAngle(new Vector3(0, 1, 0), roll)
|
||||||
|
.applyAxisAngle(new Vector3(0, 0, 1), yaw);
|
||||||
|
turretRotation.x += pitch;
|
||||||
|
turretRotation.y += roll;
|
||||||
|
turretRotation.z += yaw;
|
||||||
|
}
|
||||||
|
|
||||||
|
turretPosition.add(turretOrigin);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HeadsUpDisplay>
|
<HeadsUpDisplay>
|
||||||
<mesh>
|
<group rotation={[-Math.PI / 2, 0, model.hullYaw]}>
|
||||||
<boxGeometry />
|
{nodes.map((node) => {
|
||||||
<meshBasicMaterial color="red" />
|
const isHull = node.name.startsWith('hull_');
|
||||||
</mesh>
|
|
||||||
|
|
||||||
{/* {armorObjects.map((object) => {
|
|
||||||
const isHull = object.name.startsWith('hull_');
|
|
||||||
const isVisible = isHull;
|
const isVisible = isHull;
|
||||||
|
|
||||||
if (!isVisible) return null;
|
if (!isVisible) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ArmorMesh key={object.uuid} geometry={(object as Mesh).geometry} />
|
<ArmorMesh key={node.uuid} geometry={(node as Mesh).geometry} />
|
||||||
);
|
);
|
||||||
})} */}
|
})}
|
||||||
|
|
||||||
{/* {armorObjects.map((object) => {
|
<group position={turretPosition} rotation={turretRotation}>
|
||||||
const isCurrentTurret = object.name.startsWith(
|
{nodes.map((node) => {
|
||||||
`turret_${model.toString().padStart(2, '0')}`,
|
const isCurrentTurret = node.name.startsWith(
|
||||||
|
`turret_${turretModelDefinition.model
|
||||||
|
.toString()
|
||||||
|
.padStart(2, '0')}`,
|
||||||
);
|
);
|
||||||
const isVisible = isCurrentTurret;
|
const isVisible = isCurrentTurret;
|
||||||
|
|
||||||
if (!isVisible) return null;
|
if (!isVisible) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<mesh
|
<ArmorMesh
|
||||||
|
key={node.uuid}
|
||||||
position={turretOrigin}
|
position={turretOrigin}
|
||||||
key={object.uuid}
|
geometry={(node as Mesh).geometry}
|
||||||
geometry={(object as Mesh).geometry}
|
/>
|
||||||
>
|
|
||||||
<ArmorMesh />
|
|
||||||
</mesh>
|
|
||||||
);
|
);
|
||||||
})} */}
|
})}
|
||||||
|
|
||||||
{/* {armorObjects.map((object) => {
|
<group
|
||||||
const isCurrentGun = object.name.startsWith(
|
position={new Vector3()
|
||||||
`gun_${model.toString().padStart(2, '0')}`,
|
.sub(turretOrigin)
|
||||||
|
.sub(gunOrigin)
|
||||||
|
.applyAxisAngle(new Vector3(1, 0, 0), model.gunPitch)
|
||||||
|
.add(turretOrigin)
|
||||||
|
.add(gunOrigin)}
|
||||||
|
rotation={[model.gunPitch, 0, 0]}
|
||||||
|
>
|
||||||
|
{nodes.map((node) => {
|
||||||
|
const isCurrentGun = node.name.startsWith(
|
||||||
|
`gun_${gunModelDefinition.model.toString().padStart(2, '0')}`,
|
||||||
);
|
);
|
||||||
const isVisible = isCurrentGun;
|
const isVisible = isCurrentGun;
|
||||||
|
|
||||||
if (!isVisible) return null;
|
if (!isVisible) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<mesh
|
<ArmorMesh
|
||||||
|
key={node.uuid}
|
||||||
position={turretOrigin.clone().add(gunOrigin)}
|
position={turretOrigin.clone().add(gunOrigin)}
|
||||||
key={object.uuid}
|
geometry={(node as Mesh).geometry}
|
||||||
geometry={(object as Mesh).geometry}
|
/>
|
||||||
>
|
|
||||||
<ArmorHighlightMaterial />
|
|
||||||
</mesh>
|
|
||||||
);
|
);
|
||||||
})} */}
|
})}
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
</HeadsUpDisplay>
|
</HeadsUpDisplay>
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
@@ -1,11 +1,5 @@
|
|||||||
import { ThreeEvent, useLoader, useThree } from '@react-three/fiber';
|
import { ThreeEvent, useLoader, useThree } from '@react-three/fiber';
|
||||||
import {
|
import { useEffect, useRef, useState } from 'react';
|
||||||
forwardRef,
|
|
||||||
useEffect,
|
|
||||||
useImperativeHandle,
|
|
||||||
useRef,
|
|
||||||
useState,
|
|
||||||
} from 'react';
|
|
||||||
import {
|
import {
|
||||||
Euler,
|
Euler,
|
||||||
Group,
|
Group,
|
||||||
@@ -28,35 +22,30 @@ import mutateTankopedia, {
|
|||||||
useTankopedia,
|
useTankopedia,
|
||||||
} from '../../../../../../../stores/tankopedia';
|
} from '../../../../../../../stores/tankopedia';
|
||||||
|
|
||||||
export const TankModel = forwardRef<Group>((_props, ref) => {
|
export function TankModel() {
|
||||||
// "TypeError: dispatcher.use is not a function"
|
// "TypeError: dispatcher.use is not a function"
|
||||||
// const awaitedModelDefinitions = use(modelDefinitions);
|
// const awaitedModelDefinitions = use(modelDefinitions);
|
||||||
const [awaitedModelDefinitions, setAwaitedModelDefinitions] = useState<
|
const [awaitedModelDefinitions, setAwaitedModelDefinitions] = useState<
|
||||||
ModelDefinitions | undefined
|
ModelDefinitions | undefined
|
||||||
>(undefined);
|
>(undefined);
|
||||||
|
const canvas = useThree((state) => state.gl.domElement);
|
||||||
const protagonist = useTankopedia((state) => {
|
const protagonist = useTankopedia((state) => {
|
||||||
if (!state.areTanksAssigned) return;
|
if (!state.areTanksAssigned) return;
|
||||||
return state.protagonist;
|
return state.protagonist;
|
||||||
});
|
});
|
||||||
const canvas = useThree((state) => state.gl.domElement);
|
|
||||||
|
|
||||||
if (!protagonist) return null;
|
if (!protagonist) return null;
|
||||||
|
|
||||||
const modelGltf = useLoader(
|
const gltf = useLoader(
|
||||||
GLTFLoader,
|
GLTFLoader,
|
||||||
asset(`3d/tanks/models/${protagonist.tank.id}.glb`),
|
asset(`3d/tanks/models/${protagonist.tank.id}.glb`),
|
||||||
);
|
);
|
||||||
const armorGltf = useLoader(
|
|
||||||
GLTFLoader,
|
|
||||||
asset(`3d/tanks/armor/${protagonist.tank.id}.glb`),
|
|
||||||
);
|
|
||||||
const model = useTankopedia((state) => state.model);
|
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);
|
const gunContainer = useRef<Group>(null);
|
||||||
const nodes = Object.values(modelGltf.nodes);
|
const nodes = Object.values(gltf.nodes);
|
||||||
|
|
||||||
useImperativeHandle(ref, () => hullContainer.current!);
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
setAwaitedModelDefinitions(await modelDefinitions);
|
setAwaitedModelDefinitions(await modelDefinitions);
|
||||||
@@ -411,4 +400,4 @@ export const TankModel = forwardRef<Group>((_props, ref) => {
|
|||||||
</group>
|
</group>
|
||||||
</group>
|
</group>
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
@@ -24,7 +24,7 @@ export default function Page({ params }: { params: { id: string } }) {
|
|||||||
draft.model.hullYaw = 0;
|
draft.model.hullYaw = 0;
|
||||||
draft.model.turretYaw = 0;
|
draft.model.turretYaw = 0;
|
||||||
draft.model.gunPitch = 0;
|
draft.model.gunPitch = 0;
|
||||||
draft.mode = 'model';
|
draft.mode = 'armor';
|
||||||
draft.areTanksAssigned = true;
|
draft.areTanksAssigned = true;
|
||||||
|
|
||||||
if (!draft.areTanksAssigned) return;
|
if (!draft.areTanksAssigned) return;
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ export function ArmorMesh(props: ArmorMeshProps) {
|
|||||||
<mesh {...props} renderOrder={1}>
|
<mesh {...props} renderOrder={1}>
|
||||||
<meshBasicMaterial
|
<meshBasicMaterial
|
||||||
transparent
|
transparent
|
||||||
opacity={Math.random() / 2}
|
// opacity={Math.random() / 2}
|
||||||
|
opacity={0.25}
|
||||||
color="red"
|
color="red"
|
||||||
depthWrite={false}
|
depthWrite={false}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in new issue
Block a user