mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 18:57:03 +02:00
feat: isolated tank model
This commit is contained in:
1 parent
4a6b5e3aa9
commit
9e0c4ddb9a
10 files changed
+547
-769
No files matched your search
@@ -20,6 +20,7 @@ import { Controls } from '../Control';
|
||||
import { Lighting } from '../Lighting';
|
||||
import { RotationInputs } from '../RotationInputs';
|
||||
import { SceneProps } from '../SceneProps';
|
||||
import { TankArmor } from './components/TankArmor';
|
||||
import { TankModel } from './components/TankModel';
|
||||
|
||||
export function TankDisplay() {
|
||||
@@ -134,6 +135,7 @@ export function TankDisplay() {
|
||||
}
|
||||
>
|
||||
<TankModel ref={hullContainer} />
|
||||
<TankArmor />
|
||||
</Suspense>
|
||||
</Canvas>
|
||||
</div>
|
||||
|
||||
@@ -1,82 +1,65 @@
|
||||
import { useLoader } from '@react-three/fiber';
|
||||
import {
|
||||
forwardRef,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { Group, Mesh, Vector3 } from 'three';
|
||||
import { GLTFLoader } from 'three-stdlib';
|
||||
import { X_AXIS } from '../../../../../../../constants/axis';
|
||||
import { asset } from '../../../../../../../core/blitzkrieg/asset';
|
||||
import {
|
||||
ModelDefinitions,
|
||||
modelDefinitions,
|
||||
} from '../../../../../../../core/blitzkrieg/modelDefinitions';
|
||||
import { useTankopedia } from '../../../../../../../stores/tankopedia';
|
||||
import { forwardRef } from 'react';
|
||||
import { Group } from 'three';
|
||||
import { HeadsUpDisplay } from '../../../../../../../components/HeadsUpDisplay';
|
||||
|
||||
export const TankArmor = forwardRef<Group>((_props, ref) => {
|
||||
const hullContainer = useRef<Group>(null);
|
||||
const protagonist = useTankopedia((state) => {
|
||||
if (!state.areTanksAssigned) return;
|
||||
return state.protagonist;
|
||||
});
|
||||
const [awaitedModelDefinitions, setAwaitedModelDefinitions] = useState<
|
||||
ModelDefinitions | undefined
|
||||
>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
setAwaitedModelDefinitions(await modelDefinitions);
|
||||
})();
|
||||
}, []);
|
||||
|
||||
useImperativeHandle(ref, () => hullContainer.current!);
|
||||
|
||||
if (!awaitedModelDefinitions) return null;
|
||||
if (!protagonist) return null;
|
||||
|
||||
const tankModelDefinition = awaitedModelDefinitions[protagonist.tank.id];
|
||||
const turretModelDefinition =
|
||||
tankModelDefinition.turrets[protagonist.turret.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 gunModelDefinition = turretModelDefinition.guns[protagonist.gun.id];
|
||||
const gltf = useLoader(
|
||||
GLTFLoader,
|
||||
asset(`3d/tanks/armor/${protagonist.tank.id}.glb`),
|
||||
);
|
||||
interface TankArmorProps {}
|
||||
|
||||
export const TankArmor = forwardRef<Group, TankArmorProps>((_props, ref) => {
|
||||
return (
|
||||
<group ref={hullContainer} rotation={[-Math.PI / 2, 0, 0]}>
|
||||
{Object.values(gltf.nodes).map((node) => {
|
||||
const armor = Number(node.name.match(/.+_armor_(\d+)/)?.[1]);
|
||||
const position = new Vector3();
|
||||
<HeadsUpDisplay>
|
||||
<mesh>
|
||||
<boxGeometry />
|
||||
<meshBasicMaterial color="red" />
|
||||
</mesh>
|
||||
|
||||
if (node.name.startsWith('turret_')) position.add(turretOrigin);
|
||||
if (node.name.startsWith('gun_'))
|
||||
position.add(turretOrigin).add(gunOrigin);
|
||||
{/* {armorObjects.map((object) => {
|
||||
const isHull = object.name.startsWith('hull_');
|
||||
const isVisible = isHull;
|
||||
|
||||
if (!isVisible) return null;
|
||||
|
||||
return (
|
||||
<mesh
|
||||
scale={1 + 1e-3}
|
||||
key={node.uuid}
|
||||
geometry={(node as Mesh).geometry}
|
||||
position={position}
|
||||
>
|
||||
<meshBasicMaterial color="red" transparent opacity={0.5} />
|
||||
</mesh>
|
||||
<ArmorMesh key={object.uuid} geometry={(object as Mesh).geometry} />
|
||||
);
|
||||
})}
|
||||
</group>
|
||||
})} */}
|
||||
|
||||
{/* {armorObjects.map((object) => {
|
||||
const isCurrentTurret = object.name.startsWith(
|
||||
`turret_${model.toString().padStart(2, '0')}`,
|
||||
);
|
||||
const isVisible = isCurrentTurret;
|
||||
|
||||
if (!isVisible) return null;
|
||||
|
||||
return (
|
||||
<mesh
|
||||
position={turretOrigin}
|
||||
key={object.uuid}
|
||||
geometry={(object as Mesh).geometry}
|
||||
>
|
||||
<ArmorMesh />
|
||||
</mesh>
|
||||
);
|
||||
})} */}
|
||||
|
||||
{/* {armorObjects.map((object) => {
|
||||
const isCurrentGun = object.name.startsWith(
|
||||
`gun_${model.toString().padStart(2, '0')}`,
|
||||
);
|
||||
const isVisible = isCurrentGun;
|
||||
|
||||
if (!isVisible) return null;
|
||||
|
||||
return (
|
||||
<mesh
|
||||
position={turretOrigin.clone().add(gunOrigin)}
|
||||
key={object.uuid}
|
||||
geometry={(object as Mesh).geometry}
|
||||
>
|
||||
<ArmorHighlightMaterial />
|
||||
</mesh>
|
||||
);
|
||||
})} */}
|
||||
</HeadsUpDisplay>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,414 @@
|
||||
import { ThreeEvent, useLoader, useThree } from '@react-three/fiber';
|
||||
import {
|
||||
forwardRef,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import {
|
||||
Euler,
|
||||
Group,
|
||||
Mesh,
|
||||
MeshStandardMaterial,
|
||||
Vector2,
|
||||
Vector3,
|
||||
} from 'three';
|
||||
import { GLTFLoader } from 'three-stdlib';
|
||||
import { X_AXIS } from '../../../../../../../constants/axis';
|
||||
import { applyPitchYawLimits } from '../../../../../../../core/blitz/applyPitchYawLimits';
|
||||
import { asset } from '../../../../../../../core/blitzkrieg/asset';
|
||||
import {
|
||||
ModelDefinitions,
|
||||
modelDefinitions,
|
||||
} from '../../../../../../../core/blitzkrieg/modelDefinitions';
|
||||
import { resolveJsxTree } from '../../../../../../../core/blitzkrieg/resolveJsxTree';
|
||||
import { normalizeAnglePI } from '../../../../../../../core/math/normalizeAngle180';
|
||||
import mutateTankopedia, {
|
||||
useTankopedia,
|
||||
} from '../../../../../../../stores/tankopedia';
|
||||
|
||||
export const TankModel = forwardRef<Group>((_props, ref) => {
|
||||
// "TypeError: dispatcher.use is not a function"
|
||||
// const awaitedModelDefinitions = use(modelDefinitions);
|
||||
const [awaitedModelDefinitions, setAwaitedModelDefinitions] = useState<
|
||||
ModelDefinitions | undefined
|
||||
>(undefined);
|
||||
const protagonist = useTankopedia((state) => {
|
||||
if (!state.areTanksAssigned) return;
|
||||
return state.protagonist;
|
||||
});
|
||||
const canvas = useThree((state) => state.gl.domElement);
|
||||
|
||||
if (!protagonist) return null;
|
||||
|
||||
const modelGltf = useLoader(
|
||||
GLTFLoader,
|
||||
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 hullContainer = useRef<Group>(null);
|
||||
const turretContainer = useRef<Group>(null);
|
||||
const gunContainer = useRef<Group>(null);
|
||||
const nodes = Object.values(modelGltf.nodes);
|
||||
|
||||
useImperativeHandle(ref, () => hullContainer.current!);
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
setAwaitedModelDefinitions(await modelDefinitions);
|
||||
})();
|
||||
}, []);
|
||||
|
||||
if (!awaitedModelDefinitions) return null;
|
||||
|
||||
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 (
|
||||
<group ref={hullContainer} rotation={[-Math.PI / 2, 0, model.hullYaw]}>
|
||||
{nodes.map((node) => {
|
||||
const isHull = node.name === 'hull';
|
||||
const isWheel = node.name.startsWith('chassis_wheel_');
|
||||
const isTrack = node.name.startsWith('chassis_track_');
|
||||
const isVisible = isHull || isWheel || isTrack;
|
||||
let draftHullYaw = 0;
|
||||
|
||||
function handlePointerDown(event: ThreeEvent<PointerEvent>) {
|
||||
event.stopPropagation();
|
||||
mutateTankopedia((draft) => {
|
||||
draft.model.controlsEnabled = false;
|
||||
});
|
||||
|
||||
if (isHull) {
|
||||
draftHullYaw = model.hullYaw;
|
||||
|
||||
window.addEventListener('pointermove', handlePointerMoveHull);
|
||||
window.addEventListener('pointerup', handlePointerUpHull);
|
||||
} else if (isTrack) {
|
||||
window.addEventListener('pointermove', handlePointerMoveTrack);
|
||||
window.addEventListener('pointerup', handlePointerUpTrack);
|
||||
}
|
||||
}
|
||||
function handlePointerMoveHull(event: PointerEvent) {
|
||||
draftHullYaw += event.movementX * ((2 * Math.PI) / canvas.width);
|
||||
if (hullContainer.current) {
|
||||
hullContainer.current.rotation.z = draftHullYaw;
|
||||
}
|
||||
}
|
||||
function handlePointerUpHull() {
|
||||
mutateTankopedia((draft) => {
|
||||
draft.model.controlsEnabled = true;
|
||||
draft.model.hullYaw = normalizeAnglePI(draftHullYaw);
|
||||
});
|
||||
window.removeEventListener('pointermove', handlePointerMoveHull);
|
||||
window.removeEventListener('pointerup', handlePointerUpHull);
|
||||
}
|
||||
function handlePointerMoveTrack(event: PointerEvent) {
|
||||
const mesh = node as Mesh;
|
||||
const material = mesh.material as MeshStandardMaterial;
|
||||
const offset = new Vector2(
|
||||
0,
|
||||
event.movementY * (6 / canvas.height) +
|
||||
event.movementX * (6 / canvas.width),
|
||||
);
|
||||
|
||||
material.map?.offset.add(offset);
|
||||
material.roughnessMap?.offset.add(offset);
|
||||
material.normalMap?.offset.add(offset);
|
||||
}
|
||||
function handlePointerUpTrack() {
|
||||
mutateTankopedia((draft) => {
|
||||
draft.model.controlsEnabled = true;
|
||||
});
|
||||
window.removeEventListener('pointermove', handlePointerMoveTrack);
|
||||
window.removeEventListener('pointerup', handlePointerUpTrack);
|
||||
}
|
||||
|
||||
if (!isVisible) return null;
|
||||
return (
|
||||
<mesh
|
||||
children={resolveJsxTree(node)}
|
||||
key={node.uuid}
|
||||
castShadow
|
||||
receiveShadow
|
||||
geometry={(node as Mesh).geometry}
|
||||
material={(node as Mesh).material}
|
||||
position={node.position}
|
||||
rotation={node.rotation}
|
||||
scale={node.scale}
|
||||
onPointerDown={handlePointerDown}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
<group
|
||||
position={turretPosition}
|
||||
rotation={turretRotation}
|
||||
ref={turretContainer}
|
||||
>
|
||||
{nodes.map((node) => {
|
||||
const isTurret = node.name.startsWith('turret_');
|
||||
const isCurrentTurret =
|
||||
node.name ===
|
||||
`turret_${turretModelDefinition.model.toString().padStart(2, '0')}`;
|
||||
const isVisible = isCurrentTurret;
|
||||
let draftPitch = 0;
|
||||
let draftYaw = 0;
|
||||
|
||||
function handlePointerDown(event: ThreeEvent<PointerEvent>) {
|
||||
event.stopPropagation();
|
||||
|
||||
if (isTurret) {
|
||||
draftYaw = model.turretYaw;
|
||||
draftPitch = model.gunPitch;
|
||||
|
||||
mutateTankopedia((draft) => {
|
||||
draft.model.controlsEnabled = false;
|
||||
});
|
||||
window.addEventListener('pointermove', handlePointerMove);
|
||||
window.addEventListener('pointerup', handlePointerUp);
|
||||
}
|
||||
}
|
||||
function handlePointerMove(event: PointerEvent) {
|
||||
[draftPitch, draftYaw] = applyPitchYawLimits(
|
||||
draftPitch,
|
||||
draftYaw + event.movementX * (Math.PI / canvas.width),
|
||||
gunModelDefinition.pitch,
|
||||
turretModelDefinition.yaw,
|
||||
);
|
||||
|
||||
if (gunContainer.current) {
|
||||
gunContainer.current.position
|
||||
.set(0, 0, 0)
|
||||
.sub(turretOrigin)
|
||||
.sub(gunOrigin)
|
||||
.applyAxisAngle(new Vector3(1, 0, 0), draftPitch)
|
||||
.add(turretOrigin)
|
||||
.add(gunOrigin);
|
||||
gunContainer.current.rotation.x = draftPitch;
|
||||
}
|
||||
|
||||
if (turretContainer.current) {
|
||||
turretContainer.current.position
|
||||
.set(0, 0, 0)
|
||||
.sub(turretOrigin)
|
||||
.applyAxisAngle(new Vector3(0, 0, 1), draftYaw);
|
||||
turretContainer.current.rotation.z = draftYaw;
|
||||
|
||||
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);
|
||||
|
||||
turretContainer.current.position
|
||||
.applyAxisAngle(new Vector3(1, 0, 0), pitch)
|
||||
.applyAxisAngle(new Vector3(0, 1, 0), roll)
|
||||
.applyAxisAngle(new Vector3(0, 0, 1), yaw);
|
||||
}
|
||||
|
||||
turretContainer.current.position.add(turretOrigin);
|
||||
}
|
||||
}
|
||||
function handlePointerUp() {
|
||||
mutateTankopedia((state) => {
|
||||
state.model.controlsEnabled = true;
|
||||
state.model.gunPitch = normalizeAnglePI(draftPitch);
|
||||
state.model.turretYaw = normalizeAnglePI(draftYaw);
|
||||
});
|
||||
window.removeEventListener('pointermove', handlePointerMove);
|
||||
window.removeEventListener('pointerup', handlePointerUp);
|
||||
}
|
||||
|
||||
if (!isVisible) return null;
|
||||
return (
|
||||
<mesh
|
||||
visible={isVisible}
|
||||
onPointerDown={handlePointerDown}
|
||||
children={resolveJsxTree(node)}
|
||||
key={node.uuid}
|
||||
castShadow
|
||||
receiveShadow
|
||||
geometry={(node as Mesh).geometry}
|
||||
material={(node as Mesh).material}
|
||||
position={node.position}
|
||||
rotation={node.rotation}
|
||||
scale={node.scale}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
<group
|
||||
position={new Vector3()
|
||||
.sub(turretOrigin)
|
||||
.sub(gunOrigin)
|
||||
.applyAxisAngle(new Vector3(1, 0, 0), model.gunPitch)
|
||||
.add(turretOrigin)
|
||||
.add(gunOrigin)}
|
||||
rotation={[model.gunPitch, 0, 0]}
|
||||
ref={gunContainer}
|
||||
>
|
||||
{nodes.map((node) => {
|
||||
const isCurrentMantlet =
|
||||
node.name ===
|
||||
`gun_${gunModelDefinition.model
|
||||
.toString()
|
||||
.padStart(2, '0')}_mask`;
|
||||
const isCurrentGun =
|
||||
node.name ===
|
||||
`gun_${gunModelDefinition.model.toString().padStart(2, '0')}`;
|
||||
const isVisible = isCurrentGun || isCurrentMantlet;
|
||||
let draftPitch = 0;
|
||||
let draftYaw = 0;
|
||||
|
||||
function handlePointerDown(event: ThreeEvent<PointerEvent>) {
|
||||
event.stopPropagation();
|
||||
|
||||
mutateTankopedia((draft) => {
|
||||
draft.model.controlsEnabled = false;
|
||||
});
|
||||
draftPitch = model.gunPitch;
|
||||
draftYaw = model.turretYaw;
|
||||
window.addEventListener('pointermove', handlePointerMove);
|
||||
window.addEventListener('pointerup', handlePointerUp);
|
||||
}
|
||||
function handlePointerMove(event: PointerEvent) {
|
||||
[draftPitch, draftYaw] = applyPitchYawLimits(
|
||||
draftPitch - event.movementY * (Math.PI / canvas.height),
|
||||
draftYaw + event.movementX * (Math.PI / canvas.width),
|
||||
gunModelDefinition.pitch,
|
||||
turretModelDefinition.yaw,
|
||||
);
|
||||
|
||||
if (gunContainer.current) {
|
||||
gunContainer.current.position
|
||||
.set(0, 0, 0)
|
||||
.sub(turretOrigin)
|
||||
.sub(gunOrigin)
|
||||
.applyAxisAngle(new Vector3(1, 0, 0), draftPitch)
|
||||
.add(turretOrigin)
|
||||
.add(gunOrigin);
|
||||
gunContainer.current.rotation.x = draftPitch;
|
||||
}
|
||||
|
||||
if (turretContainer.current) {
|
||||
turretContainer.current.position
|
||||
.set(0, 0, 0)
|
||||
.sub(turretOrigin)
|
||||
.applyAxisAngle(new Vector3(0, 0, 1), draftYaw);
|
||||
turretContainer.current.rotation.z = draftYaw;
|
||||
|
||||
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);
|
||||
|
||||
turretContainer.current.position
|
||||
.applyAxisAngle(new Vector3(1, 0, 0), pitch)
|
||||
.applyAxisAngle(new Vector3(0, 1, 0), roll)
|
||||
.applyAxisAngle(new Vector3(0, 0, 1), yaw);
|
||||
}
|
||||
|
||||
turretContainer.current.position.add(turretOrigin);
|
||||
}
|
||||
}
|
||||
function handlePointerUp() {
|
||||
mutateTankopedia((state) => {
|
||||
state.model.controlsEnabled = true;
|
||||
state.model.gunPitch = normalizeAnglePI(draftPitch);
|
||||
state.model.turretYaw = normalizeAnglePI(draftYaw);
|
||||
});
|
||||
window.removeEventListener('pointermove', handlePointerMove);
|
||||
window.removeEventListener('pointerup', handlePointerUp);
|
||||
}
|
||||
|
||||
if (!isVisible) return null;
|
||||
return (
|
||||
<mesh
|
||||
visible={isVisible}
|
||||
onPointerDown={handlePointerDown}
|
||||
children={resolveJsxTree(node)}
|
||||
key={node.uuid}
|
||||
castShadow
|
||||
receiveShadow
|
||||
geometry={(node as Mesh).geometry}
|
||||
material={(node as Mesh).material}
|
||||
position={node.position}
|
||||
rotation={node.rotation}
|
||||
scale={node.scale}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</group>
|
||||
|
||||
{/* <GunContainer
|
||||
ref={gunContainer}
|
||||
armorObjects={Object.values(armorGltf.nodes)}
|
||||
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}
|
||||
modelObjects={Object.values(modelGltf.nodes)}
|
||||
turretOrigin={turretOrigin}
|
||||
/> */}
|
||||
</group>
|
||||
</group>
|
||||
);
|
||||
});
|
||||
-171
@@ -1,171 +0,0 @@
|
||||
import { ThreeEvent, useThree } from '@react-three/fiber';
|
||||
import { RefObject, forwardRef, useImperativeHandle, useRef } from 'react';
|
||||
import { Group, Mesh, Object3D, Vector3 } from 'three';
|
||||
import { ArmorHighlightMaterial } from '../../../../../../../../../components/ArmorHighlightMaterial';
|
||||
import { applyPitchYawLimits } from '../../../../../../../../../core/blitz/applyPitchYawLimits';
|
||||
import {
|
||||
InitialTurretRotation,
|
||||
PitchLimits,
|
||||
YawLimits,
|
||||
} from '../../../../../../../../../core/blitzkrieg/modelDefinitions';
|
||||
import { resolveJsxTree } from '../../../../../../../../../core/blitzkrieg/resolveJsxTree';
|
||||
import { normalizeAnglePI } from '../../../../../../../../../core/math/normalizeAngle180';
|
||||
|
||||
interface GunContainerProps {
|
||||
turretOrigin: Vector3;
|
||||
gunOrigin: Vector3;
|
||||
pitch: number;
|
||||
yaw: number;
|
||||
modelObjects: Object3D[];
|
||||
armorObjects: Object3D[];
|
||||
model: number;
|
||||
yawLimits?: YawLimits;
|
||||
pitchLimits: PitchLimits;
|
||||
onPitchStart: () => void;
|
||||
onPitchEnd: (pitch: number, yaw: number) => void;
|
||||
initialTurretRotation?: InitialTurretRotation;
|
||||
turretContainer: RefObject<Group>;
|
||||
}
|
||||
|
||||
export const GunContainer = forwardRef<Group, GunContainerProps>(
|
||||
(
|
||||
{
|
||||
gunOrigin,
|
||||
turretOrigin,
|
||||
pitch,
|
||||
initialTurretRotation,
|
||||
yaw,
|
||||
modelObjects,
|
||||
yawLimits,
|
||||
onPitchStart,
|
||||
turretContainer,
|
||||
armorObjects,
|
||||
pitchLimits,
|
||||
model,
|
||||
onPitchEnd,
|
||||
}: GunContainerProps,
|
||||
ref,
|
||||
) => {
|
||||
const canvas = useThree((state) => state.gl.domElement);
|
||||
const gunContainer = useRef<Group>(null);
|
||||
|
||||
useImperativeHandle(ref, () => gunContainer.current!);
|
||||
|
||||
return (
|
||||
<group
|
||||
position={new Vector3()
|
||||
.sub(turretOrigin)
|
||||
.sub(gunOrigin)
|
||||
.applyAxisAngle(new Vector3(1, 0, 0), pitch)
|
||||
.add(turretOrigin)
|
||||
.add(gunOrigin)}
|
||||
rotation={[pitch, 0, 0]}
|
||||
ref={gunContainer}
|
||||
>
|
||||
{modelObjects.map((object) => {
|
||||
const isCurrentMantlet =
|
||||
object.name === `gun_${model.toString().padStart(2, '0')}_mask`;
|
||||
const isCurrentGun =
|
||||
object.name === `gun_${model.toString().padStart(2, '0')}`;
|
||||
const isVisible = isCurrentGun || isCurrentMantlet;
|
||||
let draftPitch = 0;
|
||||
let draftYaw = 0;
|
||||
|
||||
function handlePointerDown(event: ThreeEvent<PointerEvent>) {
|
||||
event.stopPropagation();
|
||||
|
||||
onPitchStart();
|
||||
draftPitch = pitch;
|
||||
draftYaw = yaw;
|
||||
window.addEventListener('pointermove', handlePointerMove);
|
||||
window.addEventListener('pointerup', handlePointerUp);
|
||||
}
|
||||
function handlePointerMove(event: PointerEvent) {
|
||||
[draftPitch, draftYaw] = applyPitchYawLimits(
|
||||
draftPitch - event.movementY * (Math.PI / canvas.height),
|
||||
draftYaw + event.movementX * (Math.PI / canvas.width),
|
||||
pitchLimits,
|
||||
yawLimits,
|
||||
);
|
||||
|
||||
if (gunContainer.current) {
|
||||
gunContainer.current.position
|
||||
.set(0, 0, 0)
|
||||
.sub(turretOrigin)
|
||||
.sub(gunOrigin)
|
||||
.applyAxisAngle(new Vector3(1, 0, 0), draftPitch)
|
||||
.add(turretOrigin)
|
||||
.add(gunOrigin);
|
||||
gunContainer.current.rotation.x = draftPitch;
|
||||
}
|
||||
|
||||
if (turretContainer.current) {
|
||||
turretContainer.current.position
|
||||
.set(0, 0, 0)
|
||||
.sub(turretOrigin)
|
||||
.applyAxisAngle(new Vector3(0, 0, 1), draftYaw);
|
||||
turretContainer.current.rotation.z = draftYaw;
|
||||
|
||||
if (initialTurretRotation) {
|
||||
const pitch = -initialTurretRotation.pitch * (Math.PI / 180);
|
||||
const yaw = -initialTurretRotation.yaw * (Math.PI / 180);
|
||||
const roll = -initialTurretRotation.roll * (Math.PI / 180);
|
||||
|
||||
turretContainer.current.position
|
||||
.applyAxisAngle(new Vector3(1, 0, 0), pitch)
|
||||
.applyAxisAngle(new Vector3(0, 1, 0), roll)
|
||||
.applyAxisAngle(new Vector3(0, 0, 1), yaw);
|
||||
}
|
||||
|
||||
turretContainer.current.position.add(turretOrigin);
|
||||
}
|
||||
}
|
||||
function handlePointerUp() {
|
||||
onPitchEnd(
|
||||
normalizeAnglePI(draftPitch),
|
||||
normalizeAnglePI(draftYaw),
|
||||
);
|
||||
window.removeEventListener('pointermove', handlePointerMove);
|
||||
window.removeEventListener('pointerup', handlePointerUp);
|
||||
}
|
||||
|
||||
if (!isVisible) return null;
|
||||
return (
|
||||
<mesh
|
||||
visible={isVisible}
|
||||
onPointerDown={handlePointerDown}
|
||||
children={resolveJsxTree(object)}
|
||||
key={object.uuid}
|
||||
castShadow
|
||||
receiveShadow
|
||||
geometry={(object as Mesh).geometry}
|
||||
material={(object as Mesh).material}
|
||||
position={object.position}
|
||||
rotation={object.rotation}
|
||||
scale={object.scale}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
{armorObjects.map((object) => {
|
||||
const isCurrentGun = object.name.startsWith(
|
||||
`gun_${model.toString().padStart(2, '0')}`,
|
||||
);
|
||||
const isVisible = isCurrentGun;
|
||||
|
||||
if (!isVisible) return null;
|
||||
|
||||
return (
|
||||
<mesh
|
||||
position={turretOrigin.clone().add(gunOrigin)}
|
||||
key={object.uuid}
|
||||
geometry={(object as Mesh).geometry}
|
||||
>
|
||||
<ArmorHighlightMaterial />
|
||||
</mesh>
|
||||
);
|
||||
})}
|
||||
</group>
|
||||
);
|
||||
},
|
||||
);
|
||||
-135
@@ -1,135 +0,0 @@
|
||||
import { ThreeEvent, useThree } from '@react-three/fiber';
|
||||
import { ReactNode, forwardRef, useImperativeHandle, useRef } from 'react';
|
||||
import {
|
||||
Euler,
|
||||
Group,
|
||||
Mesh,
|
||||
MeshStandardMaterial,
|
||||
Object3D,
|
||||
Vector2,
|
||||
} from 'three';
|
||||
import { ArmorHighlightMaterial } from '../../../../../../../../../components/ArmorHighlightMaterial';
|
||||
import { resolveJsxTree } from '../../../../../../../../../core/blitzkrieg/resolveJsxTree';
|
||||
import { normalizeAnglePI } from '../../../../../../../../../core/math/normalizeAngle180';
|
||||
|
||||
interface HullContainerProps {
|
||||
children: ReactNode;
|
||||
yaw: number;
|
||||
modelObjects: Object3D[];
|
||||
armorObjects: Object3D[];
|
||||
onYawStart: () => void;
|
||||
onYawEnd: (yaw: number) => void;
|
||||
onTrackStart: () => void;
|
||||
onTrackEnd: () => void;
|
||||
}
|
||||
|
||||
export const HullContainer = forwardRef<Group, HullContainerProps>(
|
||||
(
|
||||
{
|
||||
children,
|
||||
yaw,
|
||||
armorObjects,
|
||||
modelObjects,
|
||||
onYawStart,
|
||||
onYawEnd,
|
||||
onTrackEnd,
|
||||
onTrackStart,
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const canvas = useThree((state) => state.gl.domElement);
|
||||
const hullContainer = useRef<Group>(null);
|
||||
const rotation = [-Math.PI / 2, 0, yaw] as unknown as Euler;
|
||||
|
||||
useImperativeHandle(ref, () => hullContainer.current!);
|
||||
|
||||
return (
|
||||
<group ref={hullContainer} rotation={rotation}>
|
||||
{modelObjects.map((object) => {
|
||||
const isHull = object.name === 'hull';
|
||||
const isWheel = object.name.startsWith('chassis_wheel_');
|
||||
const isTrack = object.name.startsWith('chassis_track_');
|
||||
const isVisible = isHull || isWheel || isTrack;
|
||||
let draftHullYaw = 0;
|
||||
|
||||
function handlePointerDown(event: ThreeEvent<PointerEvent>) {
|
||||
event.stopPropagation();
|
||||
|
||||
if (isHull) {
|
||||
draftHullYaw = yaw;
|
||||
|
||||
onYawStart();
|
||||
window.addEventListener('pointermove', handlePointerMoveHull);
|
||||
window.addEventListener('pointerup', handlePointerUpHull);
|
||||
} else if (isTrack) {
|
||||
onTrackStart();
|
||||
window.addEventListener('pointermove', handlePointerMoveTrack);
|
||||
window.addEventListener('pointerup', handlePointerUpTrack);
|
||||
}
|
||||
}
|
||||
function handlePointerMoveHull(event: PointerEvent) {
|
||||
draftHullYaw += event.movementX * ((2 * Math.PI) / canvas.width);
|
||||
|
||||
if (hullContainer.current) {
|
||||
hullContainer.current.rotation.z = draftHullYaw;
|
||||
}
|
||||
}
|
||||
function handlePointerUpHull() {
|
||||
onYawEnd(normalizeAnglePI(draftHullYaw));
|
||||
window.removeEventListener('pointermove', handlePointerMoveHull);
|
||||
window.removeEventListener('pointerup', handlePointerUpHull);
|
||||
}
|
||||
function handlePointerMoveTrack(event: PointerEvent) {
|
||||
const mesh = object as Mesh;
|
||||
const material = mesh.material as MeshStandardMaterial;
|
||||
const offset = new Vector2(
|
||||
0,
|
||||
event.movementY * (6 / canvas.height) +
|
||||
event.movementX * (6 / canvas.width),
|
||||
);
|
||||
|
||||
material.map?.offset.add(offset);
|
||||
material.roughnessMap?.offset.add(offset);
|
||||
material.normalMap?.offset.add(offset);
|
||||
}
|
||||
function handlePointerUpTrack() {
|
||||
onTrackEnd();
|
||||
window.removeEventListener('pointermove', handlePointerMoveTrack);
|
||||
window.removeEventListener('pointerup', handlePointerUpTrack);
|
||||
}
|
||||
|
||||
if (!isVisible) return null;
|
||||
return (
|
||||
<mesh
|
||||
children={resolveJsxTree(object)}
|
||||
key={object.uuid}
|
||||
castShadow
|
||||
receiveShadow
|
||||
geometry={(object as Mesh).geometry}
|
||||
material={(object as Mesh).material}
|
||||
position={object.position}
|
||||
rotation={object.rotation}
|
||||
scale={object.scale}
|
||||
onPointerDown={handlePointerDown}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
{armorObjects.map((object) => {
|
||||
const isHull = object.name.startsWith('hull_');
|
||||
const isVisible = isHull;
|
||||
|
||||
if (!isVisible) return null;
|
||||
|
||||
return (
|
||||
<mesh key={object.uuid} geometry={(object as Mesh).geometry}>
|
||||
<ArmorHighlightMaterial />
|
||||
</mesh>
|
||||
);
|
||||
})}
|
||||
|
||||
{children}
|
||||
</group>
|
||||
);
|
||||
},
|
||||
);
|
||||
-191
@@ -1,191 +0,0 @@
|
||||
import { ThreeEvent, useThree } from '@react-three/fiber';
|
||||
import {
|
||||
ReactNode,
|
||||
RefObject,
|
||||
forwardRef,
|
||||
useImperativeHandle,
|
||||
useRef,
|
||||
} from 'react';
|
||||
import { Euler, Group, Mesh, Object3D, Vector3 } from 'three';
|
||||
import { ArmorHighlightMaterial } from '../../../../../../../../../components/ArmorHighlightMaterial';
|
||||
import { applyPitchYawLimits } from '../../../../../../../../../core/blitz/applyPitchYawLimits';
|
||||
import {
|
||||
InitialTurretRotation,
|
||||
PitchLimits,
|
||||
YawLimits,
|
||||
} from '../../../../../../../../../core/blitzkrieg/modelDefinitions';
|
||||
import { resolveJsxTree } from '../../../../../../../../../core/blitzkrieg/resolveJsxTree';
|
||||
import { normalizeAnglePI } from '../../../../../../../../../core/math/normalizeAngle180';
|
||||
|
||||
interface TurretContainerProps {
|
||||
turretOrigin: Vector3;
|
||||
gunOrigin: Vector3;
|
||||
modelObjects: Object3D[];
|
||||
armorObjects: Object3D[];
|
||||
children: ReactNode;
|
||||
yaw: number;
|
||||
pitch: number;
|
||||
model: number;
|
||||
yawLimits?: YawLimits;
|
||||
pitchLimits: PitchLimits;
|
||||
onYawStart: () => void;
|
||||
onYawEnd: (pitch: number, yaw: number) => void;
|
||||
gunContainer: RefObject<Group>;
|
||||
initialTurretRotation?: InitialTurretRotation;
|
||||
}
|
||||
|
||||
export const TurretContainer = forwardRef(
|
||||
(
|
||||
{
|
||||
gunContainer,
|
||||
turretOrigin,
|
||||
yaw,
|
||||
children,
|
||||
modelObjects,
|
||||
yawLimits,
|
||||
model,
|
||||
onYawEnd,
|
||||
pitch,
|
||||
gunOrigin,
|
||||
armorObjects,
|
||||
pitchLimits,
|
||||
onYawStart,
|
||||
initialTurretRotation,
|
||||
}: TurretContainerProps,
|
||||
ref,
|
||||
) => {
|
||||
const canvas = useThree((state) => state.gl.domElement);
|
||||
const turretContainer = useRef<Group>(null);
|
||||
const position = new Vector3()
|
||||
.sub(turretOrigin)
|
||||
.applyAxisAngle(new Vector3(0, 0, 1), yaw);
|
||||
const rotation = new Euler(0, 0, yaw);
|
||||
|
||||
if (initialTurretRotation) {
|
||||
const pitch = -initialTurretRotation.pitch * (Math.PI / 180);
|
||||
const yaw = -initialTurretRotation.yaw * (Math.PI / 180);
|
||||
const roll = -initialTurretRotation.roll * (Math.PI / 180);
|
||||
|
||||
position
|
||||
.applyAxisAngle(new Vector3(1, 0, 0), pitch)
|
||||
.applyAxisAngle(new Vector3(0, 1, 0), roll)
|
||||
.applyAxisAngle(new Vector3(0, 0, 1), yaw);
|
||||
rotation.x += pitch;
|
||||
rotation.y += roll;
|
||||
rotation.z += yaw;
|
||||
}
|
||||
|
||||
position.add(turretOrigin);
|
||||
|
||||
useImperativeHandle(ref, () => turretContainer.current!);
|
||||
|
||||
return (
|
||||
<group position={position} rotation={rotation} ref={turretContainer}>
|
||||
{modelObjects.map((object) => {
|
||||
const isTurret = object.name.startsWith('turret_');
|
||||
const isCurrentTurret =
|
||||
object.name === `turret_${model.toString().padStart(2, '0')}`;
|
||||
const isVisible = isCurrentTurret;
|
||||
let draftPitch = 0;
|
||||
let draftYaw = 0;
|
||||
|
||||
function handlePointerDown(event: ThreeEvent<PointerEvent>) {
|
||||
event.stopPropagation();
|
||||
|
||||
if (isTurret) {
|
||||
draftYaw = yaw;
|
||||
draftPitch = pitch;
|
||||
|
||||
onYawStart();
|
||||
window.addEventListener('pointermove', handlePointerMove);
|
||||
window.addEventListener('pointerup', handlePointerUp);
|
||||
}
|
||||
}
|
||||
function handlePointerMove(event: PointerEvent) {
|
||||
[draftPitch, draftYaw] = applyPitchYawLimits(
|
||||
draftPitch,
|
||||
draftYaw + event.movementX * (Math.PI / canvas.width),
|
||||
pitchLimits,
|
||||
yawLimits,
|
||||
);
|
||||
|
||||
if (gunContainer.current) {
|
||||
gunContainer.current.position
|
||||
.set(0, 0, 0)
|
||||
.sub(turretOrigin)
|
||||
.sub(gunOrigin)
|
||||
.applyAxisAngle(new Vector3(1, 0, 0), draftPitch)
|
||||
.add(turretOrigin)
|
||||
.add(gunOrigin);
|
||||
gunContainer.current.rotation.x = draftPitch;
|
||||
}
|
||||
|
||||
if (turretContainer.current) {
|
||||
turretContainer.current.position
|
||||
.set(0, 0, 0)
|
||||
.sub(turretOrigin)
|
||||
.applyAxisAngle(new Vector3(0, 0, 1), draftYaw);
|
||||
turretContainer.current.rotation.z = draftYaw;
|
||||
|
||||
if (initialTurretRotation) {
|
||||
const pitch = -initialTurretRotation.pitch * (Math.PI / 180);
|
||||
const yaw = -initialTurretRotation.yaw * (Math.PI / 180);
|
||||
const roll = -initialTurretRotation.roll * (Math.PI / 180);
|
||||
|
||||
turretContainer.current.position
|
||||
.applyAxisAngle(new Vector3(1, 0, 0), pitch)
|
||||
.applyAxisAngle(new Vector3(0, 1, 0), roll)
|
||||
.applyAxisAngle(new Vector3(0, 0, 1), yaw);
|
||||
}
|
||||
|
||||
turretContainer.current.position.add(turretOrigin);
|
||||
}
|
||||
}
|
||||
function handlePointerUp() {
|
||||
onYawEnd(normalizeAnglePI(draftPitch), normalizeAnglePI(draftYaw));
|
||||
window.removeEventListener('pointermove', handlePointerMove);
|
||||
window.removeEventListener('pointerup', handlePointerUp);
|
||||
}
|
||||
|
||||
if (!isVisible) return null;
|
||||
return (
|
||||
<mesh
|
||||
visible={isVisible}
|
||||
onPointerDown={handlePointerDown}
|
||||
children={resolveJsxTree(object)}
|
||||
key={object.uuid}
|
||||
castShadow
|
||||
receiveShadow
|
||||
geometry={(object as Mesh).geometry}
|
||||
material={(object as Mesh).material}
|
||||
position={object.position}
|
||||
rotation={object.rotation}
|
||||
scale={object.scale}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
{armorObjects.map((object) => {
|
||||
const isCurrentTurret = object.name.startsWith(
|
||||
`turret_${model.toString().padStart(2, '0')}`,
|
||||
);
|
||||
const isVisible = isCurrentTurret;
|
||||
|
||||
if (!isVisible) return null;
|
||||
|
||||
return (
|
||||
<mesh
|
||||
position={turretOrigin}
|
||||
key={object.uuid}
|
||||
geometry={(object as Mesh).geometry}
|
||||
>
|
||||
<ArmorHighlightMaterial />
|
||||
</mesh>
|
||||
);
|
||||
})}
|
||||
|
||||
{children}
|
||||
</group>
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -1,157 +0,0 @@
|
||||
import { useLoader } from '@react-three/fiber';
|
||||
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 { asset } from '../../../../../../../../core/blitzkrieg/asset';
|
||||
import {
|
||||
ModelDefinitions,
|
||||
modelDefinitions,
|
||||
} from '../../../../../../../../core/blitzkrieg/modelDefinitions';
|
||||
import mutateTankopedia, {
|
||||
useTankopedia,
|
||||
} from '../../../../../../../../stores/tankopedia';
|
||||
import { GunContainer } from './components/GunContainer';
|
||||
import { HullContainer } from './components/HullContainer';
|
||||
import { TurretContainer } from './components/TurretContainer';
|
||||
|
||||
export const TankModel = forwardRef<Group>((_props, ref) => {
|
||||
// "TypeError: dispatcher.use is not a function"
|
||||
// const awaitedModelDefinitions = use(modelDefinitions);
|
||||
const [awaitedModelDefinitions, setAwaitedModelDefinitions] = useState<
|
||||
ModelDefinitions | undefined
|
||||
>(undefined);
|
||||
const protagonist = useTankopedia((state) => {
|
||||
if (!state.areTanksAssigned) return;
|
||||
return state.protagonist;
|
||||
});
|
||||
|
||||
if (!protagonist) return null;
|
||||
|
||||
const modelGltf = useLoader(
|
||||
GLTFLoader,
|
||||
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 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[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);
|
||||
|
||||
return (
|
||||
<HullContainer
|
||||
modelObjects={Object.values(modelGltf.nodes)}
|
||||
armorObjects={Object.values(armorGltf.nodes)}
|
||||
yaw={model.hullYaw}
|
||||
ref={hullContainer}
|
||||
onYawStart={() =>
|
||||
mutateTankopedia((draft) => {
|
||||
draft.model.controlsEnabled = false;
|
||||
})
|
||||
}
|
||||
onYawEnd={(yaw) => {
|
||||
mutateTankopedia((draft) => {
|
||||
draft.model.controlsEnabled = true;
|
||||
draft.model.hullYaw = yaw;
|
||||
});
|
||||
}}
|
||||
onTrackStart={() => {
|
||||
mutateTankopedia((draft) => {
|
||||
draft.model.controlsEnabled = false;
|
||||
});
|
||||
}}
|
||||
onTrackEnd={() => {
|
||||
mutateTankopedia((draft) => {
|
||||
draft.model.controlsEnabled = true;
|
||||
});
|
||||
}}
|
||||
>
|
||||
<TurretContainer
|
||||
armorObjects={Object.values(armorGltf.nodes)}
|
||||
initialTurretRotation={tankModelDefinition.turretRotation}
|
||||
gunOrigin={gunOrigin}
|
||||
ref={turretContainer}
|
||||
modelObjects={Object.values(modelGltf.nodes)}
|
||||
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}
|
||||
armorObjects={Object.values(armorGltf.nodes)}
|
||||
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}
|
||||
modelObjects={Object.values(modelGltf.nodes)}
|
||||
turretOrigin={turretOrigin}
|
||||
/>
|
||||
</TurretContainer>
|
||||
</HullContainer>
|
||||
);
|
||||
});
|
||||
@@ -1,42 +0,0 @@
|
||||
import { GreaterEqualDepth } from 'three';
|
||||
|
||||
const INFLATE = '0.0';
|
||||
|
||||
export function ArmorHighlightMaterial() {
|
||||
return (
|
||||
<shaderMaterial
|
||||
depthFunc={GreaterEqualDepth}
|
||||
args={[
|
||||
{
|
||||
transparent: true,
|
||||
|
||||
vertexShader: `
|
||||
varying vec3 vNormal;
|
||||
varying vec3 vViewPosition; // Camera position in view space
|
||||
|
||||
void main() {
|
||||
vNormal = normalMatrix * normal; // Transform normal to view space
|
||||
vViewPosition = (modelViewMatrix * vec4(position, 1.0)).xyz; // Camera position in view space
|
||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
||||
}
|
||||
`,
|
||||
|
||||
fragmentShader: `
|
||||
// Fragment Shader
|
||||
varying vec3 vNormal;
|
||||
varying vec3 vViewPosition;
|
||||
|
||||
void main() {
|
||||
// Use vNormal for your fragment shader calculations
|
||||
vec3 normal = normalize(vNormal);
|
||||
float uhhh = dot(normal, vViewPosition) * 0.5 + 1.0;
|
||||
|
||||
// ... rest of your fragment shader code ...
|
||||
gl_FragColor = vec4(1.0, 0.0, 0.0, uhhh);
|
||||
}
|
||||
`,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { MeshProps } from '@react-three/fiber';
|
||||
|
||||
interface ArmorMeshProps extends MeshProps {}
|
||||
|
||||
export function ArmorMesh(props: ArmorMeshProps) {
|
||||
return (
|
||||
<>
|
||||
<mesh {...props}>
|
||||
<meshBasicMaterial colorWrite={false} />
|
||||
</mesh>
|
||||
|
||||
<mesh {...props} renderOrder={1}>
|
||||
<meshBasicMaterial
|
||||
transparent
|
||||
opacity={Math.random() / 2}
|
||||
color="red"
|
||||
depthWrite={false}
|
||||
/>
|
||||
</mesh>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { createPortal, useFrame, useThree } from '@react-three/fiber';
|
||||
import { isInteger } from 'lodash';
|
||||
import { ReactNode } from 'react';
|
||||
import { Scene } from 'three';
|
||||
|
||||
interface HeadsUpDisplayProps {
|
||||
priority?: number;
|
||||
children: ReactNode;
|
||||
}
|
||||
/**
|
||||
* Renders `children` above the default scene, even if the priority is a
|
||||
* negative number.
|
||||
*/
|
||||
export function HeadsUpDisplay({
|
||||
priority = 1,
|
||||
children,
|
||||
}: HeadsUpDisplayProps) {
|
||||
const { gl: renderer, scene: defaultScene, camera } = useThree();
|
||||
const scene = new Scene();
|
||||
const portal = createPortal(children, scene, {
|
||||
events: { priority: priority + 1 },
|
||||
});
|
||||
|
||||
useFrame(() => {
|
||||
if (priority === 1) {
|
||||
renderer.clear();
|
||||
renderer.render(defaultScene, camera);
|
||||
}
|
||||
|
||||
renderer.clearDepth();
|
||||
renderer.render(scene, camera);
|
||||
}, priority);
|
||||
|
||||
renderer.autoClear = false;
|
||||
if (!isInteger(priority)) {
|
||||
throw new TypeError('Priority property must be an integer');
|
||||
}
|
||||
|
||||
return portal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this component if the default scene disappears on usage of the
|
||||
* HeadsUpDisplay; caused because there are not HeadsUpDisplay components
|
||||
* with `1` priority.
|
||||
*/
|
||||
export function RenderDefault() {
|
||||
const { gl: renderer, scene, camera } = useThree();
|
||||
|
||||
useFrame(() => renderer.render(scene, camera));
|
||||
|
||||
return null;
|
||||
}
|
||||
Reference in new issue
Block a user