mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 18:57:03 +02:00
287 lines
10 KiB
TypeScript
287 lines
10 KiB
TypeScript
import { invalidate, useThree, type ThreeEvent } from "@react-three/fiber";
|
|
import { useRef } from "react";
|
|
import { Group, Mesh, MeshStandardMaterial, Vector2 } from "three";
|
|
import { applyPitchYawLimits } from "../core/blitz/applyPitchYawLimits";
|
|
import { jsxTree } from "../core/blitzkit/jsxTree";
|
|
import { modelTransformEvent } from "../core/blitzkit/modelTransform";
|
|
import { controlsEnabledEvent } from "../core/controlsEnabled";
|
|
import { DuelModule, getDuel, useDuel } from "../hooks/useDuel";
|
|
import { useDuelModel } from "../hooks/useDuelModel";
|
|
import { hasEquipment } from "../hooks/useEquipment";
|
|
import { useModel } from "../hooks/useModel";
|
|
import { useTankTransform } from "../hooks/useTankTransform";
|
|
import { Tankopedia } from "../stores/tankopedia";
|
|
import { TankopediaDisplay } from "../stores/tankopediaPersistent/constants";
|
|
|
|
export function TankModel() {
|
|
const protagonist = Tankopedia.use((state) => state.protagonist);
|
|
const track = useDuel("protagonist", DuelModule.Track);
|
|
const turret = useDuel("protagonist", DuelModule.Turret);
|
|
|
|
const canvas = useThree((state) => state.gl.domElement);
|
|
const turretContainer = useRef<Group>(null!);
|
|
const gunContainer = useRef<Group>(null!);
|
|
const tankModelDefinition = useDuelModel("protagonist");
|
|
const trackModelDefinition = tankModelDefinition.tracks[track.id];
|
|
const turretModelDefinition = tankModelDefinition.turrets[protagonist.turret];
|
|
const gunModelDefinition = turretModelDefinition.guns[protagonist.gun];
|
|
const { gltf } = useModel(protagonist.tank);
|
|
const nodes = Object.values(gltf.nodes);
|
|
|
|
useTankTransform(track, turret, turretContainer, gunContainer);
|
|
|
|
return (
|
|
<>
|
|
{/* <mesh
|
|
scale={0.1}
|
|
position={[
|
|
trackModelDefinition.origin!.x + tankModelDefinition.turret_origin!.x,
|
|
trackModelDefinition.origin!.y + tankModelDefinition.turret_origin!.y,
|
|
trackModelDefinition.origin!.z + tankModelDefinition.turret_origin!.z,
|
|
]}
|
|
>
|
|
<sphereGeometry />
|
|
<meshBasicMaterial color="red" depthTest={false} />
|
|
</mesh> */}
|
|
|
|
{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;
|
|
const position = new Vector2();
|
|
const delta = new Vector2();
|
|
|
|
if (!isVisible) return null;
|
|
|
|
function translateTexture(offset: number) {
|
|
const mesh = node.children[0] as Mesh;
|
|
const material = mesh?.material as MeshStandardMaterial;
|
|
|
|
if (!material) return;
|
|
|
|
invalidate();
|
|
|
|
const textures = new Set([
|
|
material.map,
|
|
material.aoMap,
|
|
material.normalMap,
|
|
material.roughnessMap,
|
|
material.metalnessMap,
|
|
]);
|
|
|
|
for (const texture of textures) {
|
|
if (texture) texture.offset.y += offset;
|
|
}
|
|
}
|
|
|
|
function onPointerDown(event: ThreeEvent<PointerEvent>) {
|
|
if (isTrack && Tankopedia.state.display === TankopediaDisplay.Model) {
|
|
position.set(event.clientX, event.clientY);
|
|
event.stopPropagation();
|
|
|
|
controlsEnabledEvent.dispatch(false);
|
|
|
|
window.addEventListener("pointermove", handlePointerMove);
|
|
window.addEventListener("pointerup", handlePointerUp);
|
|
} else {
|
|
event.stopPropagation();
|
|
}
|
|
}
|
|
function handlePointerMove(event: PointerEvent) {
|
|
delta.set(event.clientX, event.clientY).sub(position);
|
|
position.set(event.clientX, event.clientY);
|
|
const deltaX = delta.x / window.innerWidth;
|
|
const deltaY = delta.y / window.innerHeight;
|
|
|
|
translateTexture(deltaX + deltaY);
|
|
}
|
|
function handlePointerUp() {
|
|
controlsEnabledEvent.dispatch(true);
|
|
|
|
window.removeEventListener("pointermove", handlePointerMove);
|
|
window.removeEventListener("pointerup", handlePointerUp);
|
|
}
|
|
|
|
return jsxTree(node, {
|
|
mesh(_, props, key) {
|
|
return (
|
|
<mesh
|
|
{...props}
|
|
key={key}
|
|
onPointerDown={onPointerDown}
|
|
castShadow
|
|
receiveShadow
|
|
/>
|
|
);
|
|
},
|
|
});
|
|
})}
|
|
|
|
<group ref={turretContainer}>
|
|
{nodes.map((node) => {
|
|
const isTurret = node.name.startsWith("turret_");
|
|
const isCurrentTurret =
|
|
node.name ===
|
|
`turret_${turretModelDefinition.model_id
|
|
.toString()
|
|
.padStart(2, "0")}`;
|
|
const isVisible = isCurrentTurret;
|
|
const position = new Vector2();
|
|
const delta = new Vector2();
|
|
|
|
if (!isVisible) return null;
|
|
|
|
function onPointerDown(event: ThreeEvent<PointerEvent>) {
|
|
event.stopPropagation();
|
|
|
|
if (!isTurret) return;
|
|
|
|
position.set(event.clientX, event.clientY);
|
|
|
|
controlsEnabledEvent.dispatch(false);
|
|
Tankopedia.mutate((draft) => {
|
|
draft.shot = undefined;
|
|
draft.highlightArmor = undefined;
|
|
});
|
|
window.addEventListener("pointermove", handlePointerMove);
|
|
window.addEventListener("pointerup", handlePointerUp);
|
|
}
|
|
function handlePointerMove(event: PointerEvent) {
|
|
const protagonist = getDuel("protagonist", DuelModule.Tank);
|
|
|
|
const hasImprovedVerticalStabilizer = hasEquipment(
|
|
122,
|
|
protagonist.equipment_preset,
|
|
Tankopedia.state.protagonist.equipment,
|
|
);
|
|
const hasDownImprovedVerticalStabilizer = hasEquipment(
|
|
124,
|
|
protagonist.equipment_preset,
|
|
Tankopedia.state.protagonist.equipment,
|
|
);
|
|
const boundingRect = canvas.getBoundingClientRect();
|
|
|
|
delta.set(event.clientX, event.clientY).sub(position);
|
|
position.set(event.clientX, event.clientY);
|
|
|
|
const [pitch, yaw] = applyPitchYawLimits(
|
|
modelTransformEvent.last!.pitch,
|
|
modelTransformEvent.last!.yaw +
|
|
delta.x * (Math.PI / boundingRect.width),
|
|
gunModelDefinition.pitch!,
|
|
turretModelDefinition.yaw,
|
|
hasImprovedVerticalStabilizer,
|
|
hasDownImprovedVerticalStabilizer,
|
|
);
|
|
modelTransformEvent.dispatch({ pitch, yaw });
|
|
}
|
|
function handlePointerUp() {
|
|
controlsEnabledEvent.dispatch(true);
|
|
window.removeEventListener("pointermove", handlePointerMove);
|
|
window.removeEventListener("pointerup", handlePointerUp);
|
|
}
|
|
|
|
return jsxTree(node, {
|
|
mesh(_, props, key) {
|
|
return (
|
|
<mesh
|
|
{...props}
|
|
key={key}
|
|
onPointerDown={onPointerDown}
|
|
castShadow
|
|
receiveShadow
|
|
/>
|
|
);
|
|
},
|
|
});
|
|
})}
|
|
|
|
<group ref={gunContainer}>
|
|
{nodes.map((node) => {
|
|
const isCurrentMantlet =
|
|
node.name ===
|
|
`gun_${gunModelDefinition.model_id
|
|
.toString()
|
|
.padStart(2, "0")}_mask`;
|
|
const isCurrentGun =
|
|
node.name ===
|
|
`gun_${gunModelDefinition.model_id.toString().padStart(2, "0")}`;
|
|
const isVisible = isCurrentGun || isCurrentMantlet;
|
|
const position = new Vector2();
|
|
const delta = new Vector2();
|
|
|
|
if (!isVisible) return null;
|
|
|
|
let pitch = 0;
|
|
let yaw = 0;
|
|
|
|
function onPointerDown(event: ThreeEvent<PointerEvent>) {
|
|
event.stopPropagation();
|
|
|
|
controlsEnabledEvent.dispatch(false);
|
|
Tankopedia.mutate((draft) => {
|
|
draft.shot = undefined;
|
|
draft.highlightArmor = undefined;
|
|
});
|
|
|
|
position.set(event.clientX, event.clientY);
|
|
pitch = modelTransformEvent.last!.pitch;
|
|
yaw = modelTransformEvent.last!.yaw;
|
|
|
|
window.addEventListener("pointermove", handlePointerMove);
|
|
window.addEventListener("pointerup", handlePointerUp);
|
|
}
|
|
function handlePointerMove(event: PointerEvent) {
|
|
const protagonist = getDuel("protagonist", DuelModule.Tank);
|
|
|
|
const hasImprovedVerticalStabilizer = hasEquipment(
|
|
122,
|
|
protagonist.equipment_preset,
|
|
Tankopedia.state.protagonist.equipment,
|
|
);
|
|
const hasDownImprovedVerticalStabilizer = hasEquipment(
|
|
124,
|
|
protagonist.equipment_preset,
|
|
Tankopedia.state.protagonist.equipment,
|
|
);
|
|
const boundingRect = canvas.getBoundingClientRect();
|
|
delta.set(event.clientX, event.clientY).sub(position);
|
|
position.set(event.clientX, event.clientY);
|
|
|
|
[pitch, yaw] = applyPitchYawLimits(
|
|
pitch - delta.y * (Math.PI / boundingRect.height),
|
|
yaw + delta.x * (Math.PI / boundingRect.width),
|
|
gunModelDefinition.pitch!,
|
|
turretModelDefinition.yaw,
|
|
hasImprovedVerticalStabilizer,
|
|
hasDownImprovedVerticalStabilizer,
|
|
);
|
|
modelTransformEvent.dispatch({ pitch, yaw });
|
|
}
|
|
function handlePointerUp() {
|
|
controlsEnabledEvent.dispatch(true);
|
|
window.removeEventListener("pointermove", handlePointerMove);
|
|
window.removeEventListener("pointerup", handlePointerUp);
|
|
}
|
|
|
|
return jsxTree(node, {
|
|
mesh(_, props, key) {
|
|
return (
|
|
<mesh
|
|
{...props}
|
|
key={key}
|
|
onPointerDown={onPointerDown}
|
|
castShadow
|
|
receiveShadow
|
|
/>
|
|
);
|
|
},
|
|
});
|
|
})}
|
|
</group>
|
|
</group>
|
|
</>
|
|
);
|
|
}
|